🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: rec.games.programmer
Article #391

CODE: VGA mode X S/Scrn and panning

#391
From: egg
Date: Mon, 03 Aug 1992 17:22
519 lines
20305 bytes
For anyone interested,

In the spirit of this news group, here is some code to set up a VGA
Mode X split screen and facilitate panning.

I think this group should be a place to seek information on techniqes and
ideas and for people to share their game programming experience (and code)
with others. I will contribute more code in future and urge that others to
the same for the benefit of all readers of this group.

Flames aint cool so if you disagree, well its your right but dont bother me.

Themie Gouthas
egg@dstos3.dsto.gov.au
-------------------------------------------------------------------------------
/* Simple Demo of MODE X Split screen and panning  */
/* Compile using Turbo C and Tasm                  */

#include <stdio.h>
#include <conio.h>
#include <ctype.h>

/* For explanation of following functions see source file comments */

extern unsigned int Set320x240Mode(unsigned int WidthInPixels);
extern void WritePixelX(int X, int Y, int PageOffset, int Color);
extern void SetStartAddr(int X, int Y, unsigned int PageOffset);
extern void SplitScrnLine(unsigned int line);
extern TextMode();

extern unsigned int SplitScrnOfs;          /* Offset in video ram of split   */
                                           /*  screen (always 0);            */
extern unsigned int MainScrnOfs;           /* Offset in video ram ov virtual */
                                           /*  screen                        */
extern unsigned int ScrnLogicalByteWidth;  /* Virtual scrn's width in bytes  */
extern unsigned int ScrnLogicalPixelWidth; /* Virtual scrn's width in pixels */
extern unsigned int ScrnLogicalHeight;     /* Virtual scrn's height in pixels*/
extern unsigned int MaxScrollX;     /* Max X pos (bytes) in virt screen of   */
                                    /* of physical screen                    */
extern unsigned int MaxScrollY;     /* Max Y pos in virt screen of           */
                                    /* of physical screen                    */

/* draw a coloured rectangle */
void draw_rect(int x, int y, int width, int color, unsigned int page){
 int i,j;
 for (j=0;j<width;j++)
   for(i=0;i<width;i++){
     WritePixelX(x+j,y+i,page,color);
   }
}

void main(){
  int i,j;
  char ch;
  unsigned int maxx;
  unsigned int curr_x=0,curr_y=0;

  printf(" Mode X Split screen and panning demo \n\n");
  printf(" Scroll: U, D, L, R    Quit: Q\n\n\nPress a key to continue");
  getch();

  /* setup Mode X 320x240x256 with a logical width of ~ 500 pixels        */
  /* we actually get 496 due to the fact that the width must be divisible */
  /* by 8                                                                 */

  maxx = Set320x240Mode(500);  /* maxx is set to 496 */


  SplitScrnLine(160);

  /* draw a row of blue and white rectangles at top of split screen */
  for (i=0;i<24;i++)
    draw_rect(i*20,0,20,15+(i%2)*19,SplitScrnOfs);

  /* fill virtual screen with coloured rectangles */
  for (j=0;j<10;j++)
    for (i=0;i<24;i++)
      draw_rect(i*20,j*20,20,j*20+i,MainScrnOfs);


  /* Allow the user to scroll the screen                 */
  /* U = Up,  D = Down,  L = Left,  R = Right,  Q = Quit */

  do {
    ch=toupper(getch());
    switch (ch) {
      case 'U':curr_y=(curr_y==0)?0:--curr_y;
               SetStartAddr(curr_x,curr_y,MainScrnOfs);
               break;
      case 'D':curr_y=(curr_y==MaxScrollY)?curr_y:++curr_y;
               SetStartAddr(curr_x,curr_y,MainScrnOfs);
               break;
      case 'L':curr_x=(curr_x==0)?0:--curr_x;
               SetStartAddr(curr_x,curr_y,MainScrnOfs);
               break;
      case 'R':curr_x=(curr_x==MaxScrollX)?curr_x:++curr_x;
               SetStartAddr(curr_x,curr_y,MainScrnOfs);
               break;
    }
  } while (ch != 'Q');

  TextMode();
  printf("Logical screen Width in pixels = %d \n",ScrnLogicalPixelWidth);
  printf("Logical screen width in bytes  = %d \n",ScrnLogicalByteWidth);
  printf("Logical screen height          = %d \n",ScrnLogicalHeight);
  printf("Split screen offset    = %d \n",SplitScrnOfs);
  printf("Virtual screen offset  = %d \n",MainScrnOfs);
  printf("Max start X coord (byte) of physical screen = %d\n",MaxScrollX);
  printf("Max start Y coord of physical screen        = %d\n",MaxScrollY);
}

------------------------------------------------------------------------
; Panning and split screen functions for MODE X 320x240 256 Col.
; Compile with Tasm.
; C callable.


LOCALS

MAP_MASK      equ    02h     ;index in SC of Map Mask register
SC_INDEX      equ    03c4h   ;Sequence Controller Index
GC_INDEX      equ    03ceh   ; Graphics controller Index

MISC_OUTPUT   equ    06h     ;Miscellaneous Output register
SCREEN_SEG    equ    0a000h  ;segment of display memory in mode X
INPUT_STATUS_0 equ   03dah   ;Input status 0 register
INPUT_STATUS_1 equ   03dah   ;Input status 1 register


CRTC_INDEX    equ    03d4h   ;CRT Controller Index
OVERFLOW      equ    07h     ; CRTC overflow register index
MAX_SCAN_LINE equ    09h     ; CRTC maximum scan line register index
ADDR_HIGH     equ    0ch     ;Index of Start Address High reg in CRTC
ADDR_LOW      equ    0dh     ;                       Low
CRTC_OFFSET   equ    13h     ; CRTC offset register index
UNDERLINE     equ    14h     ; CRTC underline location register index
MODE_CONTROL  equ    17h     ; CRTC mode control register index
LINE_COMPARE  equ    18h     ; CRTC line compare reg. index (bits 0-7 of
                             ; split screen scan line

PHYSICAL_HEIGHT     equ    240  ; Physical screen height
PHYSICAL_WIDTH      equ    320  ; Physical screen width

        .model  small

        .data

        CRTParms label  word
        dw      00d06h  ;vertical total
        dw      03e07h  ;overflow (bit 8 of vertical counts)
        dw      04109h  ;cell height (2 to double-scan)
        dw      0ea10h  ;v sync start
        dw      0ac11h  ;v sync end and protect cr0-cr7
        dw      0df12h  ;vertical displayed
        dw      00014h  ;turn off dword mode
        dw      0e715h  ;v blank start
        dw      00616h  ;v blank end
        dw      0e317h  ;turn on byte mode
        CRT_PARM_LENGTH equ     (($-CRTParms)/2)


         public _SplitScrnOfs
         public _MainScrnOfs
         public _ScrnLogicalByteWidth
         public _ScrnLogicalPixelWidth
         public _ScrnLogicalHeight
         public _MaxScrollX
         public _MaxScrollY

        _SplitScrnOfs            dw 0   ; Offset in video ram of Split Screen
                                        ; always = 0
        _MainScrnOfs             dw 0   ; Ofset in video ram of Main virtual
                                        ; screen ( = 0 if no split screen
                                        ; otherwise = offset of first byte
                                        ; after split screen
        _ScrnLogicalByteWidth    dw 0   ; Logical width in bytes of screen
        _ScrnLogicalPixelWidth   dw 0   ; Logical width in pixels of screen
        _ScrnLogicalHeight       dw 0   ; Logical Height of screen
        _MaxScrollX              dw 0   ; Max X start position of Physical
                                        ; screen within virtual screen (in
                                        ; bytes)
        _MaxScrollY              dw 0   ; Max Y start position of Physical
                                        ; screen within virtual screen

; Index/data pairs for CRT Controller registers that differ between
; mode 13h and mode X.


        .code

        public _Set320x240Mode
        public _WritePixelX
        public _SetStartAddr
        public _TextMode
        public _SplitScrnLine

; Macro to wait for the vertical retrace leading edge

WaitForVsync   macro
        mov     dx,INPUT_STATUS_0
@@WaitNotVsync:
        in      al,dx
        test    al,08h
        jnz     @@WaitNotVsync
@@WaitVsync:
        in      al,dx
        test    al,08h
        jnz     @@WaitVsync
        endm

; Mode X (320x240, 256 colors) Set 320x240 mode with a larger virtual
;   logical screen width.
; C near-callable as:
;
;    int Set320x240(unsigned int WidthInPixels);
;
; returns the actual width of the allocated virtual screen in pixels
; and also saves it in _ScrnLogicalPixelWidth. Saves virtual screen byte
; screen width in _ScrnLogicalByteWidth.
;
;
; NOTE: Slightly modified code - Originally by Michael Abrash from DDJ.


_Set320x240Mode proc    near
        ARG     scrwidth:word
        push    bp      ;preserve caller's stack frame
        mov     bp,sp
        push    si      ;preserve C register vars
        push    di      ; (don't count on BIOS preserving anything)

        mov     ax,13h  ;let the BIOS set standard 256-color
        int     10h     ; mode (320x200 linear)

        mov     dx,SC_INDEX
        mov     ax,0604h
        out     dx,ax   ;disable chain4 mode
        mov     ax,0100h
        out     dx,ax   ;synchronous reset while setting Misc Output
                        ; for safety, even though clock unchanged
        mov     dx,MISC_OUTPUT
        mov     al,0e3h
        out     dx,al   ;select 25 MHz dot clock & 60 Hz scanning rate

        mov     dx,SC_INDEX
        mov     ax,0300h
        out     dx,ax   ;undo reset (restart sequencer)

        mov     dx,CRTC_INDEX ;reprogram the CRT Controller
        mov     al,11h  ;VSync End reg contains register write
        out     dx,al   ; protect bit
        inc     dx      ;CRT Controller Data register
        in      al,dx   ;get current VSync End register setting
        and     al,7fh  ;remove write protect on various
        out     dx,al   ; CRTC registers
        dec     dx      ;CRT Controller Index
        cld
        mov     si,offset CRTParms ;point to CRT parameter table
        mov     cx,CRT_PARM_LENGTH ;# of table entries
SetCRTParmsLoop:
        lodsw           ;get the next CRT Index/Data pair
        out     dx,ax   ;set the next CRT Index/Data pair
        loop    SetCRTParmsLoop

        mov     dx,SC_INDEX
        mov     ax,0f02h
        out     dx,ax   ;enable writes to all four planes
        mov     ax,SCREEN_SEG ;now clear all display memory, 8 pixels
        mov     es,ax         ; at a time
        sub     di,di   ;point ES:DI to display memory
        sub     ax,ax   ;clear to zero-value pixels
        mov     cx,8000h ;# of words in display memory
        rep     stosw   ;clear all of display memory

        ;  Mode X is set, now set the required logical page width.

        mov     dx,CRTC_INDEX
        mov     al,CRTC_OFFSET
        out     dx,al
        inc     dx

        mov     ax,[scrwidth]
        shr     ax,1
        shr     ax,1
        shr     ax,1
        out     dx,al

        ; The EXACT logical pixel width may not have been possible since
        ; it should be divisible by 8. Round down to the closest possible
        ; width and update the status variables

        shl     ax,1
        mov     bx,ax
        mov     [_ScrnLogicalByteWidth],ax  ; Store the byte width of virtual
                                            ; screen
        sub     ax,PHYSICAL_WIDTH/4         ; Calculate and store Max Y position
        mov     [_MaxScrollX],ax            ; of physical screen in virtual
                                            ; screen
        mov     ax,bx                       ; set ax to byte width of virt scrn
        shl     ax,1                        ; convert to pixels
        shl     ax,1
        mov     [_ScrnLogicalPixelWidth],ax ; store virt scrn pixel width
        mov     cx,ax                       ; save ax (return value)

        ; calculate no. non split screen rows in video ram
        mov     ax,0ffffh             ; cx = Maximum video ram offset
        sub     dx,dx                 ; DX:AX is divide operand,  set DX = 0
        div     bx                    ; divide ax by ScrnLogicalByteWidth
        mov     [_ScrnLogicalHeight],ax  ; Save Screen Logical Height
        sub     ax,PHYSICAL_HEIGHT       ; Update the maximum Y position of
        mov     [_MaxScrollY],ax         ; Physical screen in logical screen
        mov     ax,cx                 ; restore ax (return value)

        pop     di      ;restore C register vars
        pop     si
        pop     bp      ;restore caller's stack frame
        ret
_Set320x240Mode endp



; Mode X (320x240, 256 colors) write pixel routine.
; No clipping is performed.
; C near-callable as:
;    void WritePixelX(int X, int Y, int PageOffset, int Color);
;
; Modified code - originally by Michael Abrash from DDJ.
;
        public  _WritePixelX
        ARG X:word,Y:word,PgOfs:word,Color:word
_WritePixelX    proc    near
        push    bp      ;preserve caller's stack frame
        mov     bp,sp   ;point to local stack frame

        mov     ax,[_ScrnLogicalByteWidth]
        mul     [Y]                 ;offset of pixel's scan line in page
        mov     bx,[X]
        shr     bx,1
        shr     bx,1                ;X/4 = offset of pixel in scan line
        add     bx,ax               ;offset of pixel in page
        add     bx,[PgOfs]         ;offset of pixel in display memory
        mov     ax,SCREEN_SEG
        mov     es,ax               ;point ES:BX to the pixel's address

        mov     cl,byte ptr [X]
        and     cl,011b             ;CL = pixel's plane
        mov     ax,0100h + MAP_MASK ;AL = index in SC of Map Mask reg
        shl     ah,cl               ;set only the bit for the pixel's
                                    ; plane to 1
        mov     dx,SC_INDEX         ;set the Map Mask to enable only the
        out     dx,ax               ; pixel's plane

        mov     al,byte ptr [Color]
        mov     es:[bx],al          ;draw the pixel in the desired color

        pop     bp                  ;restore caller's stack frame
        ret
_WritePixelX    endp


; Mode X (320x240, 256 colors) Set 320x240 mode split screen starting row
; The split screen resides on the bottom half of the screen and has a
; starting address of A000:0000
;
; C near-callable as:
;    void SetStartAddr(unsigned int Addr);
;
; Updates _MainScrnOffset to reflect the existence of the split screen region
; ie -MainScrnOffset is set to the offset of the first pixel beyond the split
; screen region
;

_SplitScrnLine proc near
        ARG Line:word
        push    bp
        mov     bp,sp       ; set up stack frame

        mov     ax,[Line]
        dec     ax          ; Don't ask me why. It works this way !!
        jns     @@NotNeg    ; Check that Split Scrn start scan line is +ve

        mov     ax,0        ; Since -ve set to 0

@@NotNeg:
        push    ax          ; Save the decremented start scam line
        shl     ax,1        ; Mode X is actually composed of 480 scan lines
                            ; so for start scanline multiply required ModeX
                            ; scan line by 2

        mov     [Line],ax   ; save the scanline

        WaitForVsync        ; wait for vertical retrace

        mov     dx,CRTC_INDEX
        mov     ah,byte ptr [Line]
        mov     al,LINE_COMPARE

        cli                  ; Dont allow register setting to be interrupted

        out     dx,ax        ; Bits 7-0 of the split screen scan line

        mov     ah,byte ptr [Line+1]
        and     ah,1
        mov     cl,4
        shl     ah,cl
        mov     al,OVERFLOW  ; Bit 4 of overflow register = Bit 8 of split
        out     dx,al        ; screen scan line,
        inc     dx           ; So using readability of VGA registers
        in      al,dx        ; Read the OVERFLOW register, and set the
        and     al, not 10h  ; bit corresponding to Bit 8 (above)
        or      al,ah
        out     dx,al

        dec     dx
        mov     ah,byte ptr [Line+1]
        and     al,2
        mov     cl,3
        ror     ah,cl
        mov     al,MAX_SCAN_LINE  ; Bit 6 of max scan line register =
        out     dx,al             ; Bit 9 of split screen scan line
        inc     dx                ; As we did before, update the apropriate
        in      al,dx             ; bit without disturbing the rest
        and     al, not 40h
        or      al,ah
        out     dx,al
        sti                  ; Registers are set, so interrupts are safe

        pop     ax                    ; Determine where the first byte
        sub     ax,PHYSICAL_HEIGHT    ; of the non split screen video ram
        neg     ax                    ; starts and store it for future
        mov     bx,[_ScrnLogicalByteWidth]  ; reference
        mul     bx
        mov     [_MainScrnOfs],ax

        ; calculate no. non split screen rows in video ram
        mov     cx,0ffffh             ; cx = Maximum video ram offset
        sub     cx,ax                 ; cx = cx - _MainScrnOfs
        xchg    cx,ax                 ; swap cx and ax
        sub     dx,dx                 ; DX:AX is divide operand,  set DX = 0
        div     bx                    ; divide ax (prev cx) by
                                      ; ScrnLogicalByteWidth

        mov     [_ScrnLogicalHeight],ax  ; Save Screen Logical Height
        sub     ax,PHYSICAL_HEIGHT       ; Update the maximum Y position of
        mov     [_MaxScrollY],ax         ; Physical screen in logical screen
        xchg    cx,ax                 ; restore original ax (MainScrnOfs)

        mov     bh,al                 ; Set the visible screen start address
        mov     ch,ah                 ; to the top left corner of the virtual
        jmp     short StartAddrEntry  ; screen
        ;pop    bp
        ;ret
_SplitScrnLine  endp

; Mode X (320x240, 256 colors) Set 320x240 mode start address
;   of logical screen.
; C near-callable as:
;
;    void SetStartAddr(unsigned int x, unsigned int y, unsigned int StartOffs);
;
; Params: StartOffset is offset of first byte of logical screen ram
;           (Useful if you want to double buffer by splitting your non
;            split screen video ram into 2 pages)
;        X,Y coordinates of the top left hand corner of the physical screen
;           within the logical screen
;           X must not exceed (Logical screen width - Physical screen width)
;           Y must not exceed (Logical screen height - Physical screen height)
;
; NOTE: X in bytes NOT pixels (1 Byte = 4 pixels)

_SetStartAddr proc near
        ARG x:word,y:word,StartOffs
        push    bp
        mov     bp,sp

        mov     ax,[_ScrnLogicalByteWidth]     ; Calculate Offset increment
        mul     [y]                            ; for Y
        add     ax,[StartOffs]                 ; add it to Start offset
        add     ax,[x]                         ; add the column offset for X

        mov     bh,al                          ; setup CRTC start addr regs and
                                               ; values in word registers for
        mov     ch,ah                          ; fast word outs


StartAddrEntry:
        mov     bl,ADDR_LOW
        mov     cl,ADDR_HIGH

        WaitForVsync

        mov     dx,CRTC_INDEX
        mov     ax,bx
        out     dx,ax   ;start address low
        mov     ax,cx
        out     dx,ax   ;start address high

        pop    bp
        ret
_SetStartAddr   endp


;
; Return to text mode
;
_TextMode proc near
       push  bp
       mov   bp,sp
       mov   ax,00003h   ; Restore Text Mode
       int   10h
       ;mov   ah,4ch     ; Terminate the program
       ;int   21h
       pop   bp
       ret
_TextMode endp


        end
Technical Details
Message-ID: <1992Aug3.112214.164281@dstos3.dsto.gov.au>
Path: arc1-ca.pugleaf.net!sparky!uunet!munnari.oz.au!yoyo.aarnet.edu.au!dstos3.dsto.gov.au!egg

Show Full Headers
Complete Article Headers
Path: arc1-ca.pugleaf.net!sparky!uunet!munnari.oz.au!yoyo.aarnet.edu.au!dstos3.dsto.gov.au!egg
From: egg@dstos3.dsto.gov.au
Newsgroups: rec.games.programmer
Subject: CODE: VGA mode X S/Scrn and panning
Message-ID: <1992Aug3.112214.164281@dstos3.dsto.gov.au>
Date: 3 Aug 92 17:22:14 GMT
Organization: Defence Science and Technology Organisation
Lines: 519