;****************************************************************************
;*
;*						   SuperVGA Test Library
;*
;*                  Copyright (C) 1993 Kendall Bennett.
;*							All rights reserved.
;*
;* Filename:	$RCSfile: svga16m.asm $
;* Version:		$Revision: 1.2 $
;*
;* Language:	80386 Assembler
;* Environment:	IBM PC (MS DOS)
;*
;* Description:	This source file contains code to initialise the SuperVGA
;*				for bank switching and extended page flipping.
;*
;*				It also contains code to draw pixels, clear the display
;*				and perform page flipping for SuperVGA 16m color modes.
;*
;* $Id: svga16m.asm 1.2 1993/03/07 04:05:36 kjb Exp $
;*
;* Revision History:
;* -----------------
;*
;* $Log: svga16m.asm $
;* Revision 1.2  1993/03/07  04:05:36  kjb
;* Bug fixes.
;*
;* Revision 1.1  1993/03/03  10:26:06  kjb
;* Initial revision
;*
;****************************************************************************

		IDEAL
		JUMPS
		P386					; Use 80386 instructions

INCLUDE "model.mac"				; Memory model macros

header		init

INCLUDE	"MGRAPH.EQU"			; Include Equates for Mgraph Routines

CRTC			EQU	3D4h		; Port of CRTC registers
VGABufferSeg	EQU	0A000h		; Segment of VGA display memory
FIRSTMODE		EQU	grSVGA_320x200x16m
MAXMODE			EQU	grSVGA_1280x1024x16m

begcodeseg	svga16m

; Globals used by driver

OldBIOSMode			db	0			; Old video mode before graphics
Old50Lines			db	0			; 1 if old mode was 50 line VGA
CntDriver			dw	0			; Graphics driver number
CntMode				dw	0			; Graphics mode number
CntChipID			dw	0			; Graphics driver chip ID
CntColors			dw	gr16MColor	; This is a 16 million color driver

; The following information held in the status status area changes depending
; on which mode the driver is set up to be used in:

StatusArea:

XRes				dw	0			; Device resolution in x direction - 1
YRes				dw	0			; Device resolution in y direction - 1
BytesPerLine		dw	0			; Number of bytes in a line
PageSize			dd	0			; Graphics page size

MaxPage				dw	0			; Maximum number of video pages
OriginOffset		dw	0			; Offset of 0,0 into buffer

; Here we set up a series of tables that can be used to fill in the start of
; the status area for each mode supported by this driver, and the address's
; of the routines required for each particular driver mode:

ModeTabStart:

ModeSVGA_320x200:	dw	319			; XRes
					dw	199			; YRes
					dw	960			; BytesPerLine
ModeSVGA_640x350:	dw	639			; XRes
					dw	349			; YRes
					dw	2048		; BytesPerLine
ModeSVGA_640x400:	dw	639			; XRes
					dw	399			; YRes
					dw	2048		; BytesPerLine
ModeSVGA_640x480:	dw	639			; XRes
					dw	479			; YRes
					dw	2048		; BytesPerLine
ModeSVGA_800x600:	dw	799			; XRes
					dw	599			; YRes
					dw	2400		; BytesPerLine
ModeSVGA_1024x768:	dw	1023		; XRes
					dw	767			; YRes
					dw	3072		; BytesPerLine
ModeSVGA_1280x1024:	dw	1279		; XRes
					dw	1023		; YRes
					dw	3840		; BytesPerLine
ModeTabSize			=	($-ModeSVGA_1280x1024)	; Size of table in bytes

INCLUDE "SV_PORTS.ASM"
INCLUDE "SV_BANKS.ASM"
INCLUDE "SV_MODES.ASM"
INCLUDE "SV_PAGE.ASM"
INCLUDE "SV_MAXPG.ASM"

;----------------------------------------------------------------------------
; int _initSuperVGA(int driver,int chipID,int mode,int memory)
;----------------------------------------------------------------------------
; Routine to initialise the bank switching code for the SuperVGA, and setup
; internal tables of information about the video mode. If the video mode
; is not supported, we return -1.
;
; The value returned is a status number, where bit 0 represents extended
; page flipping is available, bit 1 that separate read/write banks are
; available.
;----------------------------------------------------------------------------
procstart	__initSuperVGA

		ARG		driver:WORD, ChipID:WORD, mode:WORD, memory:WORD

		enter	0,0
		push	si
		push	di

; Save the driver number and chip ID for later

		mov		ax,[ChipID]
		mov		[CntChipID],ax
		mov		ax,[driver]
		mov		[CntDriver],ax

; Load the Status area with info for the currently selected mode:

		mov		ax,[mode]
		mov		[CntMode],ax
		cmp		ax,MAXMODE			; AX := desired mode
		jg		@@Invalid			; invalid if greater than maximum
		cmp		ax,FIRSTMODE
		jl		@@Invalid			; invalid if less than first mode

		mov		cx,ModeTabSize		; Put size of table into cx
		sub		ax,FIRSTMODE
		mul		cl					; AX := Mode * ModeTabSize
		mov		si,ax
		lea		si,[ModeTabStart + si]
		push	cs
		pop		es					; Set up es to point to code seg
		lea		di,[StatusArea]		; DI := Start of status area

		cld
	rep	movs 	[BYTE es:di],[BYTE es:si]

		mov		ax,[driver]
		mov		bx,[mode]
		call	loadSVGAMode		; Load the SuperVGA video mode
		or		ax,ax
		jz		@@Invalid			; Mode not supported on this adapter!

		mov		[maxpage],0			; Clear maxpage variable
		call	SetupBanks			; Setup SuperVGA bank switching
		call	[InitSVGA]			; Setup bank switching code
		call	SetupPaging			; Setup SuperVGA page flipping
		mov		ax,0				; Assume no paging available
		jc		@@Done				; Extended page flipping not available

; Determine the number of pages available for the mode and the video page
; size.

		xor		ebx,ebx
		mov		bx,[memory]
		shl		ebx,10				; EBX := video memory in bytes
		mov		ax,[mode]
		call	numPages			; Calculate the number of video pages
		dec		ax
		mov		[MaxPage],ax		; Save maximum page number
		mov		[DWORD pageSize],ebx
		mov		[WORD bytesPerLine],cx
		mov		ax,1				; Extended paging is available

@@Done:
		mov		bx,[TwoBanks]
		shl		bx,1
		or		ax,bx				; Set the two banks flag
		jmp		@@Exit

@@Invalid:
		mov		ax,-1				; Return failure!

@@Exit:
		pop		di
		pop		si
		leave
		ret

procend		__initSuperVGA

;----------------------------------------------------------------------------
; int _setSuperVGAMode(void);
;----------------------------------------------------------------------------
; Routine sets the system into the SuperVGA graphics mode setup by the
; initSuperVGA routine. Note that this routine remembers if the EGA/VGA
; 43/50 line mode was set.
;
; If the video mode was not correctly set, we return false.
;----------------------------------------------------------------------------
procstart	__setSuperVGAMode

		push	bp					; INT 10h Kills this!!!
		push	si					; so save all regs...
		push	di
		push	ds

		mov		ah,0Fh				; Get current video mode service
		int		10h
		mov		[OldBIOSMode],al	; Save old video mode
		mov		[Old50Lines],0		; Default to non-50 line mode

		mov		ax,1130h			; AH := INT 10h function number
									; AL := Get character gen information
		mov		bh,00				; Get contents of INT 1Fh
		xor		dl,dl				; Clear dl
		int		10h					; Determine number of lines (in dl)
		cmp		dl,49				; 50 line mode?
		jne		@@SetMode			; No, must have been 25 lines
		mov		[Old50Lines],1		; Yes, 50 line mode was on

@@SetMode:
		mov		ax,[CntDriver]
		mov		bx,[CntMode]
		call	loadSVGAMode		; AX,BX := correct values for mode
		int		10h					; Set the video mode

		mov		ax,40h
		mov		es,ax
		xor		ax,ax
		cmp		[BYTE es:49h],3		; Mode is still text mode, did not set
		jbe		@@Exit

		call	[InitSVGA]			; Initialise bank switching on SuperVGA's
		mov		ax,1				; Mode was correctly set

@@Exit:
		pop		ds
		pop		di					; Restore regs
		pop		si
		pop		bp					; Restore bp (after INT 10 trashes it)
		ret

procend		__setSuperVGAMode

;----------------------------------------------------------------------------
; void restoreMode(void)
;----------------------------------------------------------------------------
; Routine restores the original video mode that was set before graphics mode
; was entered.
;----------------------------------------------------------------------------
procstart	_restoreMode

		push	bp					; INT 10h kills bp sometimes
		push	si					; Save all regs...
		push	di

		call	[ExitSVGA]			; Uninitialise the SuperVGA

		mov		ah,0				; Set video mode service
		mov		al,[OldBIOSMode]	; Get old BIOS mode number
		int		10h					; Set the video mode

		cmp		[Old50Lines],0		; Was 50 line mode set?
		je		@@Exit				; No, don't set it up

; Load video BIOS 8x8 characters into alphanumeric character generator

		mov		ax,1112h			; AH := INT 10h function number
									; AL := 8x8 character set load
		mov		bl,0				; BL := block to load
		int		10h					; load 8x8 characters into RAM

@@Exit:
		pop		di					; Restore regs
		pop		si
		pop		bp					; Restore bp
		ret

procend		_restoreMode

;----------------------------------------------------------------------------
; void _getVideoInfo(int *xres,int *yres,int *bytesperline,int *maxpage)
;----------------------------------------------------------------------------
; Returns information about the currently selected video mode. The video
; mode must have been set for this info to be entirely valid.
;----------------------------------------------------------------------------
procstart	__getVideoInfo

		ARG		_xres:DWORD, _yres:DWORD, _bytesperline:DWORD, _maxpage:DWORD

		enter	0,0

		les		bx,[_xres]
		mov		ax,[xres]
		mov		[es:bx],ax		; Return the x resolution

		les		bx,[_yres]
		mov		ax,[yres]
		mov		[es:bx],ax		; Return the y resolution

		les		bx,[_bytesperline]
		mov		ax,[bytesperline]
		mov		[es:bx],ax		; Return the bytes per line value

		les		bx,[_maxpage]
		mov		ax,[maxpage]
		mov		[es:bx],ax		; Return the maximum page number

		leave
		ret

procend		__getVideoInfo

;----------------------------------------------------------------------------
; void putPixel(int x,int y,long color)
;----------------------------------------------------------------------------
; Routine sets the value of a pixel in native VGA graphics modes.
;
; Entry:		x		-	X coordinate of pixel to draw
;				y		-	Y coordinate of pixel to draw
;				color	-	Color of pixel to draw
;
;----------------------------------------------------------------------------
procstart	_putPixel

		ARG		x:WORD, y:WORD, color:DWORD

		push	bp					; Set up stack frame
		mov		bp,sp

		mov		ax,[y]
		mul		[BytesPerLine]
		mov		bx,[x]
		add		ax,bx
		adc		dx,0
		shl		bx,1
		add		ax,bx
		adc		dx,0				; DX:AX := y * BytesPerLine + x * 3
		add		ax,[OriginOffset]
		adc		dl,[BYTE BankOffset]; DL := bank number
		mov		bx,ax				; BX := Offset in buffer
		cmp		dl,[BYTE CurBank]
		je		@@NoChange
		mov		al,dl
		call	[NewBank]

@@NoChange:
		mov		ax,VGABufferSeg
		mov		es,ax				; ES:BX := byte address of pixel
		mov		al,[BYTE color]
		mov		[es:bx],al     		; Replace the first byte
		cmp		bx,0FFFFh
		jne		@@NotSplit1

; Arrghh!! We have a case where a single pixel can be split across a
; bank boundary, if the bytes per line value is 1920. This can cause the
; machine to hang (and produce strange pixels).

		inc		dl
		mov		al,dl
		call	[NewBank]			; Change video banks

@@NotSplit1:
		mov		al,[BYTE color+1]
		mov		[es:bx+1],al		; Replace the middle
		cmp		bx,0FFFEh
		jne		@@NotSplit2

		inc		dl
		mov		al,dl
		call	[NewBank]			; Change video banks

@@NotSplit2:
		mov		al,[BYTE color+2]
		mov		[es:bx+2],al		; Replace the last byte
		pop		bp
		ret

procend		_putPixel

;----------------------------------------------------------------------------
; void clear(void)
;----------------------------------------------------------------------------
; Routine to clear the screen. Assumes pages begin on bank boundaries
; for simplicity of coding.
;----------------------------------------------------------------------------
procstart	_clear

		push	di
		mov		ax,[YRes]
		inc		ax
		mul		[BytesPerLine]		; DX:AX := number of bytes to fill
		mov		bx,ax				; BX := bytes in last bank to fill
		mov		dh,dl				; DH := number of full banks to fill

		mov		ax,VGABufferSeg
		mov		es,ax
		xor		di,di				; ES:DI -> video buffer
		mov		dl,[BYTE BankOffset]; DL := starting bank number
		cld							; Moves go up in memory

; Fill all of the full 64k banks first

@@OuterLoop:
		mov		al,dl
		call	[NewBank]
		xor		eax,eax				; Clear to black
		mov		cx,4000h			; Need to set 4000h double words per bank
	rep	stosd
		inc		dl
		dec		dh
		jnz		@@OuterLoop

; Now fill the last partial bank

		mov		al,dl
		call	[NewBank]
		xor		eax,eax				; Clear to black
		mov		cx,bx
		shr		cx,2				; CX := number of double words to set
	rep	stosd

		pop		di
		ret

procend		_clear

;----------------------------------------------------------------------------
; void setActivePage(int which)
;----------------------------------------------------------------------------
; Routine to set the video page for active output.
;
; Entry:		page	- Page number of page to use
;
;----------------------------------------------------------------------------
procstart	_setActivePage

		ARG		which:WORD

		push	bp					; Set up stack frame
		mov		bp,sp

; Calculate 18 bit address of page in video memory

		xor		eax,eax
		mov		ax,[which]			; EAX := page number
		mul		[PageSize]			; EDX:EAX := result
		mov		[OriginOffset],ax	; Save video buffer offset
		shr		eax,16
		mov		[BankOffset],ax		; Save video bank offset

		pop		bp
		ret

procend		_setActivePage

;----------------------------------------------------------------------------
; void setVisualPage(int which)
;----------------------------------------------------------------------------
; Routine to set the visible video page.
;
; Entry:		page	- Page number of page to use
;
;----------------------------------------------------------------------------
procstart	_setVisualPage

		ARG		which:WORD

		push	bp					; Set up stack frame
		mov		bp,sp
		push	si

; Calculate 18 bit address of page in video memory

		xor		eax,eax
		mov		ax,[which]			; EAX := page number
		mul		[PageSize]			; EDX:EAX := result
		cmp		[CntDriver],grSVGA
		je		@@HaveVESA

; For Non-VESA SuperVGA's we need to shift the page offset right 2 bits
; for 32k color video modes, so that the origin is located on a
; plane boundary.

		shr		eax,2

@@HaveVESA:
		mov		bx,ax				; BX := bottom 16 bits of address
		shr		eax,16
		push	ax					; Save top 2 bits of address

; Wait for display enable to be active (active low), to be sure that
; both halves of the start address will take place in one frame. We
; preload a few values here to save time after the DE has been
; detected.

		mov		cl,0Ch				; CL := Start Address High register
		mov		ch,bh				; CH := high byte of new address
		mov		bh,bl				; BH := low byte of new address
		mov		bl,0Dh				; BL := Start Address Low register
		mov		dx,03DAh			; DX := video status port

@@WaitDE:
		in		al,dx
		test	al,1
		jnz		@@WaitDE			; Wait for Display Enable

		cli
		pop		si					; SI := Bits 16+ for SuperVGA's
		call	[NewPage]			; Program the start address
		sti

; Now wait for the start of the vertical sync, to ensure that the old
; page will be invisible before anything is drawn on it.

		mov		dx,03DAh			; DX := video status port
@@WaitStartVert:
		in		al,dx				; Wait for start of vertical retrace
		test	al,8
		jz		@@WaitStartVert

@@Exit:
		pop		si
		pop		bp
		ret

procend		_setVisualPage

;----------------------------------------------------------------------------
; void setBank(int bank)
;----------------------------------------------------------------------------
; Sets the current read and write bank to the specified 64k bank number.
;----------------------------------------------------------------------------
procstart	_setBank

		ARG		bank:WORD

		enter	0,0

		mov		al,[BYTE bank]
		cmp		al,[BYTE CurBank]
		je		@@NoChange

		call	[NewBank]

@@NoChange:
		leave
		ret

procend		_setBank

;----------------------------------------------------------------------------
; void setReadBank(int bank)
;----------------------------------------------------------------------------
; Sets the current read bank to the specified 64k bank. If you wish to set
; the read and write banks separately, call setBank() first, followed by
; setReadBank().
;----------------------------------------------------------------------------
procstart	_setReadBank

		ARG		bank:WORD

		enter	0,0

		mov		al,[BYTE bank]
		call	[ReadBank]

		leave
		ret

procend		_setReadBank

endcodeseg	svga16m

		END
