;-----------------------------------------------------------------------
; MODULE XMAIN
;
; Initialization, panning and split screen functions for all MODE X 256
; Color resolutions
;
; Compile with Tasm.
; C callable.
;
; MODE X Source Addapted from Themie Gouthas's
;   XLIB - Mode X graphics library
;-----------------------------------------------------------------------


include xlib.inc
include xmain.inc


	.data


; Mode X CRTC register tweaks for various resolutions


X320Y200 label  word
	db      00      ; 0e3h    ; dot clock
	db      02      ; Number of CRTC Registers to update
	dw      00014h  ; turn off dword mode
	dw      0e317h  ; turn on byte mode
	dw      320     ; width
	dw      200     ; height

LAST_X_MODE         equ    13
ModeTable label word            ; Mode X tweak table
	dw      offset X320Y200     ;0

PARAMS label byte

	_CurrXMode               dw 0   ; Current graphics mode index
	_InGraphics              db 0   ; Flag indicating graphics activity
	_ScrnPhysicalByteWidth   dw 0   ; Physical width in bytes of screen
	_ScrnPhysicalPixelWidth  dw 0   ; Physical width in pixels of screen
	_ScrnPhysicalHeight      dw 0   ; Physical Height of screen
	_ErrorValue              db 0   ; Set after function calls


	_SplitScrnActive         db 0   ; Flag indicating Split scrn activity
	_DoubleBufferActive      dw 0   ; Flag indicating double buffering
	_TrippleBufferActive     dw 0   ; Flag indicating tripple buffering

	_SplitScrnScanLine       dw 0   ; Split Screen's starting scan line
	_SplitScrnVisibleHeight  dw 0   ; Split Screen's height on screen

	_SplitScrnOffs           dw 0   ; Offset in video ram of Split Screen
									; always = 0
	_Page0_Offs              dw 0   ; Ofset in video ram of Main virtual
									; screen ( = 0 if no split screen
									; otherwise = offset of first byte
									; after split screen
	_Page1_Offs              dw 0   ; Ofset in video ram of Second virtual
									; screen ( = 0 if no split screen
									; otherwise = offset of first byte
									; after split screen
									; = Page0_Offs if Doubble buffering
									; not enabled
	_Page2_Offs             dw 0

	_NonVisual_Offs         dw 0   ; Ofset in video ram of first byte
									; of non visible ram
	_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

	_VisiblePageIdx         dw 0   ; Index of currently visible D.B.
									; page

	PageAddrTable label word
	_VisiblePageOffs        dw 0   ; Table containing starting offsets
	_HiddenPageOffs         dw 0   ; of the double buffer pages
	_WaitingPageOffs        dw 0

	_TopClip                dw 0   ; Clipping Rectangle
	_BottomClip             dw 0   ;
	_LeftClip               dw 0   ; Left/Right coordinates in bytes
	_RightClip              dw 0   ;
	_PhysicalStartByteX     dw 0   ; X byte coord of physical screen
									; relative to virtual virtual screen
	_PhysicalStartPixelX    dw 0   	; X pixel coord of physical screen
									; relative to virtual screen
	_PhysicalStartY        	dw 0   	; Y pixel coord of physical screen
									; relative to virtual screen

									; NEW
	_VsyncHandlerActive     dw 0
	_MouseRefreshFlag       dw 0
	_MouseVsyncHandler      dd 0
	_StartAddressFlag       dw 0
	_WaitingStartLow        dw 0
	_WaitingStartHigh       dw 0
	_WaitingPelPan          dw 0
	_VsyncPaletteStart      dw 0
	_VsyncPaletteCount      dw 0
	_VsyncPaletteBuffer     label  byte
				db  768  dup(?)


PARAMS_END label byte

PARAM_COUNT equ ($-PARAMS)


; Index/data pairs for CRT Controller registers that differ between
; mode 13h and mode X.

	;Pelpan values for 0,1,2,3 pixel panning to the left, respectively
	PelPanMask              db      000h,002h,004h,006h

DoubleScanFlag db ?     ; Flag to indicate double scanned mode

	.code

;-------------------------------------------------------------------------
; Local Logical Screen Width setting function
; cx = Requitrd Logical Width
;
; WARNING: no registers are preserved

SetLogicalScrWidth proc
	mov   dx,CRTC_INDEX
	mov   al,CRTC_OFFSET
	out   dx,al
	inc   dx

	mov   ax,cx
	cmp   ax,[_ScrnPhysicalPixelWidth]; Is logical width >= physical width
	jge   @@ValidLogicalWidth          ; yes - continue
	mov   ax,bx                        ; no - set logical width = physical

@@ValidLogicalWidth:
	shr   ax,3
	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
	mov   [_RightClip],ax             ; Set default Right clip column
					  ; screen
	sub   ax,[_ScrnPhysicalByteWidth] ; Calculate and store Max X position
	shl   ax,2                        ; of physical screen in virtual
	mov   [_MaxScrollX],ax            ; screen in pixels
	mov   ax,bx                       ; set ax to byte width of virt scrn
	shl   ax,2                        ; convert to pixels
	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
	mov   [_BottomClip],ax         ; Set default bottom clip row
	sub   ax,[_ScrnPhysicalHeight] ; Update the maximum Y position of
	mov   [_MaxScrollY],ax         ; Physical screen in logical screen
	mov   ax,cx                    ; restore ax (return value)

	; calculate initial NonVisual
	mov  ax,[_ScrnLogicalByteWidth]
	mul  [_ScrnPhysicalHeight]
	mov  [_NonVisual_Offs],ax

@@Done: ret
SetLogicalScrWidth endp

_clear_vram proc
		push  di
	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

	WaitVsyncEnd

	sub   ax,ax               ; clear to zero-value pixels
	mov   cx,0FFFFh           ; # of words in display memory
	rep   stosw               ; clear all of display memory
		pop   di
	ret
_clear_vram endp

;-----------------------------------------------------------------------
; Mode X graphics mode set with a virtual screen
;   logical screen width.
; C near-callable as:
;
;    int x_set_mode(unsigned int mode,unsigned int WidthInPixels);
;
; returns the actual width of the allocated virtual screen in pixels
; if a valid mode was selected otherwise returns -1
;
; Saves virtual screen pixel width in _ScrnLogicalPixelWidth.
; Saves virtual screen byte  width in _ScrnLogicalByteWidth.
; Physical screen dimensions are set in _ScrnPhysicalPixelWidth,
; _ScrnPhysicalByteWidth and _ScrnPhysicalHeight
;
;
; Modes:  0  = 320 x 200  (256 color)  NOTE: Some of these modes require
;------------------------------------------------------------------------
_SetModeX proc
	ARG   mode:word,logicalscrwidth: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)

	cld
	mov   ax,ds
	mov   es,ax
	mov   di,offset PARAMS
	xor   ax,ax
	mov   cx,PARAM_COUNT
	rep   stosb

	mov   cx,[mode]
	cmp   cx,LAST_X_MODE        ; have we selected a valid mode
	jle   @@ValidMode           ; Yes !

	mov   [_InGraphics],FALSE   ; No return -1
	mov   ax,-1
	pop   di
	pop   si
	pop   bp
	ret

@@ValidMode:

	mov   [_CurrXMode],cx
	mov   [_InGraphics],TRUE

	xor   al,al
	cmp   cx,3
	jg    @@SetDoubleScanFlag
	mov   al,TRUE
@@SetDoubleScanFlag:
	mov   [DoubleScanFlag],al
	push  cx                    ; some bios's dont preserve cx

    call  _clear_vram

	mov   ax,13h                ; let the BIOS set standard 256-color
	int   10h                   ;  mode (320x200 linear)


	pop   cx

	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   bx,offset ModeTable
	shl   cx,1
	add   bx,cx
	mov   si, word ptr [bx]
	lodsb

	or    al,al
	jz    @@DontSetDot
	mov   dx,MISC_OUTPUT
	out   dx,al               ; select the dot clock and Horiz
				  ;  scanning rate
@@DontSetDot:
	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,07fh             ; remove write protect on various
	out   dx,al               ; CRTC registers
	dec   dx                  ; CRT Controller Index
	cld
	xor   cx,cx
	lodsb
	mov   cl,al

@@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

	;  Set pysical screen dimensions

	lodsw                               ; Load scrn pixel width
	mov   [_ScrnPhysicalPixelWidth],ax  ;  from tweak table and store
	mov   [_SplitScrnScanLine],ax       ; No splitscrn ==
					    ; splitscrn=PhysicalscrnHeight
	mov   bx,ax                         ; Copy width for later use
	shr   ax,2                          ; Convert to byte width
	mov   [_ScrnPhysicalByteWidth],ax   ; Store for later use
	lodsw                               ; Load Screen Phys. Height
	mov   [_ScrnPhysicalHeight],ax      ; Store for later use


	;  Mode X is set, now set the required logical page width.

	mov     cx,[logicalscrwidth]

	call    SetLogicalScrWidth

	pop     di      ;restore C register vars
	pop     si
	pop     bp      ;restore caller's stack frame
	ret
_SetModeX endp

;-----------------------------------------------------------------------
; Mode X (256 color mode) Page flip primer
; No clipping is performed.
; C near-callable as:
;
;    void x_page_flip(unsigned int x, unsigned int y);
;
; Swaps visible and hidden page offsets and then executes the SetStartAddr
; to achieve a page flip.
;
; SEE x_set_start_addr below
;
; Written by Themie Gouthas
;------------------------------------------------------------------------

_FlipPage proc
	ARG x:word,y:word
	push  bp                  ;preserve caller's stack frame
	mov   bp,sp               ;point to local stack frame
	push  si

	mov  si,[x]
	mov  ax,[_ScrnLogicalByteWidth]     ; Calculate Offset increment
	mov  cx,[y]
	mul  cx                             ; for Y
@@IdxOk:
	mov  [_VisiblePageIdx],bx
	jmp  short PageFlipEntry2
_FlipPage endp

;-----------------------------------------------------------------------
; Mode X (256 color mode) Set Mode X non split screen start address
;   of logical screen.
; C near-callable as:
;
;    void x_set_start_addr(unsigned int x, unsigned int y);
;
; 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)
;
;
; Written by Themie Gouthas,
; Parts addapted from M. Abrash code published in DDJ Mag.
;------------------------------------------------------------------------
_x_set_start_addr proc
	ARG x:word,y:word
	push bp
	mov  bp,sp
	push si

	mov  si,[x]
	mov  ax,[_ScrnLogicalByteWidth]     ; Calculate Offset increment
	mov  cx,[y]                         ; for Y
	mul  cx
	cmp  [_DoubleBufferActive],TRUE     ; Do we have double buffering ?
	je   @@PageResolution
	cmp  [_TrippleBufferActive],TRUE
	je   @@PageResolution
PageFlipEntry1:
	add  ax,[_Page0_Offs]               ; no - add page 0 offset
	jmp  short @@AddColumn

PageFlipEntry2:

	mov  [_PhysicalStartPixelX],si
	mov  [_PhysicalStartY],cx

@@PageResolution:
	add  ax,[_VisiblePageOffs]          ; Add visible page offset

@@AddColumn:
	mov  cx,si
	shr  cx,2
	mov  [_PhysicalStartByteX],cx
	add  ax,cx                          ; 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
	and  si,0003h             ; select pel pan register value for the
	mov  ah,PelPanMask[si]    ; required x coordinate
	mov  al,PEL_PANNING+20h
	mov  si,ax

	cmp  [_VsyncHandlerActive],TRUE
	jne   @@NoVsyncHandler
; NEW STUFF
@@WaitLast:
	cmp   [_StartAddressFlag],0
	jne   @@WaitLast
	cli
	mov  [_WaitingStartLow],bx
	mov  [_WaitingStartHigh],cx
	mov  [_WaitingPelPan],si
	mov  [_StartAddressFlag],1
	sti
	jmp  short @@Return

@@NoVsyncHandler:
	mov  dx,INPUT_STATUS_0    ;Wait for trailing edge of Vsync pulse
@@WaitDE:
	in   al,dx
	test al,01h
	jnz  @@WaitDE            ;display enable is active low (0 = active)

	mov  dx,CRTC_INDEX
	mov  ax,bx
	cli
	out  dx,ax               ;start address low
	mov  ax,cx
	out  dx,ax               ;start address high
	sti

; Now wait for vertical sync, so the other page will be invisible when
; we start drawing to it.
	mov  dx,INPUT_STATUS_0    ;Wait for trailing edge of Vsync pulse
@@WaitVS:
	in   al,dx
	test al,08h
	jz @@WaitVS           ;display enable is active low (0 = active)


	mov  dx,AC_INDEX
	mov  ax,si                ; Point the attribute controller to pel pan
	cli
	out  dx,al                ; reg. Bit 5 also set to prevent blanking
	mov  al,ah
	out  dx,al                ; load new Pel Pan setting.
	sti

@@Return:
	mov  [_ErrorValue],OK
	pop  si
	pop  bp
	ret
_x_set_start_addr  endp

;-----------------------------------------------------------------------
; Wait for Vertical sync
_x_wait_vsync proc
	push  bp
	WaitVsyncStart
	pop   bp
	ret
_x_wait_vsync endp


	end
