'===========================================================================
' Subject: VGA 640X480 PANNING                Date: 10-04-96 (09:52)       
'  Author: Douglas H. Lusher                  Code: QB, QBasic, PDS        
'  Origin: comp.lang.basic.misc             Packet: EGAVGA.ABC
'===========================================================================
' Greetings, fellow BASIC programmers.
' I just finished working out how to do pixel-by-pixel scrolling
' on a VGA card and I thought that this might interest some of
' you, so here's a quick demo. What this code does is this: it
' creates a virtual screen much larger than the usual SCREEN 12
' 640x480, then prints graphics to this larger virtual screen.
' When you press a key, the graphics stop being printed and you
' can use the arrow keys to pan around and observe different
' parts of the virtual screen.

' When the VGA card sets up a screen mode, the Offset Register
' &H3D5, index &H13 contains the amount of display memory to be
' used per horizontal scan line. The virtual screen is made wider
' than the ordinary screen by increasing this value. The Start
' Address High and Start Address Low registers, &H3D5 index &HC
' & &HD contain the MSB and LSB respectively of the address in
' video memory where the displayed screen begins. Increasing or
' decreasing this value by 1 pans the screen left or right by
' 8 pixels. Changing this value by the amount of memory per scan
' line pans the screen up or down. Notice that horizontal panning
' by this method alone moves the screen 8 pixels at a time. To
' pan horizontally a pixel at a time involves the Horizontal
' PEL Panning register &H3C0 index &H13. Rotating the values of
' this register from 0 through 7 pans the screen pixel-column 
' by pixel-column. Note that this register must only be changed
' during a vertical retrace and that an INP(&H3DA) command must
' preceed a change to this register. Also, bit 5 must always be
' set.

' It is worth noting also that the normal BASIC graphics commands
' will not work on the larger virtual screen, so I have provided
' my own code to create the graphics in this demo. While the code
' for these graphics commands is good, it is just BASIC and is 
' not necessarily optimised, so I realized that it is slow. That
' particular aspect is not the focus of this demo.

 DECLARE SUB QPrint (X%, Y%, Text$, Culler%)
 DECLARE SUB DrawCircle (X%, Y%, Radius%, Culler%)
 DECLARE SUB PutPixel (X%, Y%, Culler%)
 DECLARE SUB DrawLine (X1%, Y1%, X2%, Y2%, Culler%)
 DEFINT A-Z
 REDIM BitMask%(0 TO 7)
 Mask% = 128
 FOR Bit% = 0 TO 7
   BitMask%(Bit%) = Mask%: Mask% = Mask% \ 2
 NEXT

 RANDOMIZE TIMER
 SCREEN 12: ScrnWid% = 640: ScrnHgt% = 480

 'the width of the virtual screen in pixels:
 VScrnWid% = 832
 'In SCREEN 12, the following must be true:
 '  VScrnWid% must be equal to or greater than 640
 '  VScrnWid% must be less than or equal to 1088
 '  VScrnWid% must be evenly divisible by 16
 HPanLimit% = VScrnWid% - ScrnWid%
 BytesPerLine% = VScrnWid% \ 8

 'the height of the virtual screen in pixels:
 VScrnHgt% = 624
 'In SCREEN 12, the following must be true:
 '  VScrnHgt% must be equal to or greater than 480
 '  VScrnHgt% must be less than or equal to 819
 '  The product of VScrnHgt% multiplied by VScrnWid% must be less
 '      than or equal to 524288
 VPanLimit% = VScrnHgt% - ScrnHgt%

 'create the virtual screen:
 OUT &H3D4, &H13: OUT &H3D5, VScrnWid% \ 16

 'put up some graphics
 MinX% = 0: MaxX% = VScrnWid% - 1
 MinY% = 0: MaxY% = VScrnHgt% - 1
 CALL DrawLine(MinX%, MinY%, MaxX%, MinY%, 1)
 CALL DrawLine(MaxX%, MinY%, MaxX%, MaxY%, 2)
 CALL DrawLine(MaxX%, MaxY%, MinX%, MaxY%, 3)
 CALL DrawLine(MinX%, MaxY%, MinX%, MinY%, 4)

 RR% = VScrnHgt%: IF VScrnWid% < VScrnHgt% THEN RR% = VScrnWid%
 DO UNTIL LEN(INKEY$)
   X1% = INT(RND * VScrnWid%)
   Y1% = INT(RND * VScrnHgt%)
   X2% = INT(RND * VScrnWid%)
   Y2% = INT(RND * VScrnHgt%)
   C% = INT(RND * 14) + 1
   CALL DrawLine(X1%, Y1%, X2%, Y2%, C%)
   R% = INT(RND * (RR% \ 4))
   X1% = INT(RND * (VScrnWid% - R% * 2)) + R%
   Y1% = INT(RND * (VScrnHgt% - R% * 2)) + R%
   C% = INT(RND * 14) + 1
   CALL DrawCircle(X1%, Y1%, R%, C%)
 LOOP
 CALL QPrint(8, 8, "Upper Left", 15)
 CALL QPrint(VScrnWid% - 96, 8, "Upper Right", 15)
 CALL QPrint(8, VScrnHgt% - 16, "Lower Left", 15)
 CALL QPrint(VScrnWid% - 96, VScrnHgt% - 16, "Lower Right", 15)

 'allow the user to pan around the virtual screen
 X% = 0: Y% = 0
 DO
  DO WHILE LEN(INKEY$): LOOP
  DO: KP$ = INKEY$: LOOP UNTIL LEN(KP$)
  KP% = ASC(KP$): IF KP% = 0 THEN KP% = -ASC(MID$(KP$, 2))
  SELECT CASE KP%
    CASE 27
      EXIT DO
    CASE 52, -75  'Four, LArrow
      IF X% > 0 THEN
        X% = X% - 1
        DO: LOOP WHILE (INP(&H3DA) AND 8) = 0
        OUT &H3C0, &H33: OUT &H3C0, X% MOD 8
      ELSE
        BEEP
      END IF
    CASE 54, -77   'Six, RArrow
      IF X% < HPanLimit% THEN
        X% = X% + 1
        DO: LOOP WHILE (INP(&H3DA) AND 8) = 0
        OUT &H3C0, &H33: OUT &H3C0, X% MOD 8
      ELSE
        BEEP
      END IF
    CASE 56, -72  'Eight, UpArrow
      IF Y% > 0 THEN
        Y% = Y% - 1
      ELSE
        BEEP
      END IF
    CASE 50, -80  'Two, DnArrow
      IF Y% < VPanLimit% THEN
        Y% = Y% + 1
      ELSE
        BEEP
      END IF
  END SELECT
  Ptr% = (Y% * BytesPerLine%) + (X% \ 8)
  OUT &H3D4, &HD: OUT &H3D5, Ptr% MOD 256
  OUT &H3D4, &HC: OUT &H3D5, Ptr% \ 256
 LOOP
 SCREEN 0: WIDTH 80
 END

 SUB DrawCircle (X%, Y%, Radius%, Culler%)
 'a routine to draw circles using only integers and integer math
 'an implementation of Bresenham's algorithm
 'by Douglas H. Lusher, 05-09-1996
 A% = 0
 B% = Radius%
 D% = (1 - Radius%) * 2
 XX1% = X%: YY1% = Y% + B%
 XX2% = X%: YY2% = Y% - B%
 XY1% = X% + B%: YX1% = Y%
 XY2% = X% - B%: YX2% = Y%
 DO WHILE B% >= A%
   CALL PutPixel(XX1%, YY1%, Culler%)
   CALL PutPixel(XX1%, YY2%, Culler%)
   CALL PutPixel(XX2%, YY1%, Culler%)
   CALL PutPixel(XX2%, YY2%, Culler%)
   CALL PutPixel(XY1%, YX1%, Culler%)
   CALL PutPixel(XY1%, YX2%, Culler%)
   CALL PutPixel(XY2%, YX1%, Culler%)
   CALL PutPixel(XY2%, YX2%, Culler%)
   IF D% + B% > 0 THEN
     B% = B% - 1
     D% = D% - (B% * 2) + 1
     YY1% = YY1% - 1: YY2% = YY2% + 1
     XY1% = XY1% - 1: XY2% = XY2% + 1
   END IF
   IF A% > D% THEN
     A% = A% + 1
     D% = D% + (A% * 2) + 1
     XX1% = XX1% + 1: XX2% = XX2% - 1
     YX1% = YX1% + 1: YX2% = YX2% - 1
   END IF
 LOOP
 END SUB
 
 SUB DrawLine (X1%, Y1%, X2%, Y2%, Culler%)
 'a routine to draw lines using only integers and integer math
 'an implementation of Bresenham's algorithm
 'by Douglas H. Lusher, 05-08-1996
 A% = X2% - X1%
 B% = Y2% - Y1%
 DX2% = 1: DY2% = 1
 IF A% < 0 THEN A% = -A%: DX2% = -1
 IF B% < 0 THEN B% = -B%: DY2% = -1
 DX1% = DX2%: DY1% = 0
 IF A% < B% THEN SWAP A%, B%: DX1% = 0: DY1% = DY2%
 I1% = B% * 2
 D% = I1% - A%
 I2% = D% - A%
 X% = X1%: Y% = Y1%
 FOR I% = 0 TO A%
  CALL PutPixel(X%, Y%, Culler%)
  IF D% < 0 THEN
    X% = X% + DX1%
    Y% = Y% + DY1%
    D% = D% + I1%
  ELSE
    X% = X% + DX2%
    Y% = Y% + DY2%
    D% = D% + I2%
  END IF
 NEXT
 END SUB

 SUB PutPixel (X%, Y%, Culler%)
 SHARED BitMask%(), BytesPerLine%
 DEF SEG = &HA000
 Offset& = (CLNG(Y%) * BytesPerLine%) + (X% \ 8)
 OUT &H3CE, 5: OUT &H3CF, 2
 OUT &H3CE, 8: OUT &H3CF, BitMask%(X% AND 7)
 Byte% = PEEK(Offset&): POKE Offset&, Culler%
 END SUB

 SUB QPrint (X%, Y%, Text$, Culler%)
 'this routine uses the VGA hardware to print text
 '  in 16 color graphics modes
 'by Douglas H. Lusher, 06-08-1996
 SHARED BytesPerLine%

 ' 8 x 8 char box, CGA
 CharSegment% = &HFFA6: CharOffset% = &HE: CharHgt% = 8

 ' 8 x 16 char box, VGA
 'DIM Regs AS RegTypeX
 'Regs.AX = &H1130
 'Regs.BX = &H600
 'CALL InterruptX(&H10, Regs, Regs)
 'CharSegment% = Regs.ES: CharOffset% = Regs.BP: CharHgt% = 16

 REDIM BitPattern%(1 TO CharHgt%)
 Temp& = (CLNG(Y%) * BytesPerLine%) + (X% \ 8)
 VideoSegment% = &HA000 + (Temp& \ 16)
 VideoOffset% = (Temp& MOD 16) - 1
 OUT &H3CE, 5: OUT &H3CF, 2: OUT &H3CE, 8
 FOR Char% = 1 TO LEN(Text$)
   Ptr% = CharHgt% * ASC(MID$(Text$, Char%, 1)) + CharOffset% - 1
   DEF SEG = CharSegment%
   FOR Ln% = 1 TO CharHgt%
     BitPattern%(Ln%) = PEEK(Ptr% + Ln%)
   NEXT
   VideoPtr% = VideoOffset% + Char%
   DEF SEG = VideoSegment%
   FOR Ln% = 1 TO CharHgt%
     OUT &H3CF, BitPattern%(Ln%)
     Byte% = PEEK(VideoPtr%): POKE VideoPtr%, Culler%
     VideoPtr% = VideoPtr% + BytesPerLine%
   NEXT
 NEXT
 END SUB
