; Program to illustrate high-speed text-drawing operation of
;  write mode 3 of the VGA.
;  Draws a string of 8x14 characters at arbitrary locations
;  without disturbing the background, using VGA's 8x14 ROM font.
;  Designed for use with modes 0Dh, 0Eh, 0Fh, 10h, and 12h.
; Runs only on VGAs (in Models 50 & up and IBM Display Adapter
;  and 100% compatibles).
; Assembled with MASM
; By Michael Abrash
;
stack	segment para stack 'STACK'
	db	512 dup(?)
stack	ends
;
VGA_VIDEO_SEGMENT	equ	0a000h	    ;VGA display memory segment
SCREEN_WIDTH_IN_BYTES	equ	044ah	    ;offset of BIOS variable
FONT_CHARACTER_SIZE	equ	14	    ;# bytes in each font char
;
; VGA register equates.
;
SC_INDEX		equ	3c4h	    ;SC index register
SC_MAP_MASK		equ	2	    ;SC map mask register index
GC_INDEX		equ	3ceh	    ;GC index register
GC_SET_RESET		equ	0	    ;GC set/reset register index
GC_ENABLE_SET_RESET	equ	1	    ;GC enable set/reset register index
GC_ROTATE		equ	3	    ;GC data rotate/logical function
					    ; register index
GC_MODE			equ	5	    ;GC Mode register
GC_BIT_MASK		equ	8	    ;GC bit mask register index
;
dseg	segment para common 'DATA'
TEST_TEXT_ROW		equ	69	    ;row to display test text at
TEST_TEXT_COL		equ	17	    ;column to display test text at
TEST_TEXT_COLOR		equ	0fh	    ;high intensity white
TestString	label	byte
	db	'Hello, world!',0	    ;test string to print.
FontPointer	dd	?		    ;font offset
dseg	ends
;
cseg	segment para public 'CODE'
	assume	cs:cseg, ds:dseg
start	proc	near
	mov	ax,dseg
	mov	ds,ax
;
; Select 640x480 graphics mode.
;
	mov	ax,012h
	int	10h
;
; Set the screen to all blue, using the readability of VGA registers
; to preserve reserved bits.
;
	mov	dx,GC_INDEX
	mov	al,GC_SET_RESET
	out	dx,al
	inc	dx
	in	al,dx
	and	al,0f0h
	or	al,1		;blue plane only set, others reset
	out	dx,al
	dec	dx
	mov	al,GC_ENABLE_SET_RESET
	out	dx,al
	inc	dx
	in	al,dx
	and	al,0f0h
	or	al,0fh		;enable set/reset for all planes
	out	dx,al
	mov	dx,VGA_VIDEO_SEGMENT
	mov	es,dx		;point to display memory
	mov	di,0
	mov	cx,8000h	;fill all 32k words
	mov	ax,0ffffh	;because of set/reset, the value
				; written actually doesn't matter
	rep stosw		    ;fill with blue
;
; Set driver to use the 8x14 font.
;
	mov	ah,11h		;VGA BIOS character generator function,
	mov	al,30h		; return info subfunction
	mov	bh,2		;get 8x14 font pointer
	int	10h
	call	SelectFont
;
; Print the test string.
;
	mov	si,offset TestString
	mov	bx,TEST_TEXT_ROW
	mov	cx,TEST_TEXT_COL
	mov	ah,TEST_TEXT_COLOR
	call	DrawString
;
; Wait for a key, then set to text mode & end.
;
	mov	ah,1
	int	21h		;wait for a key
	mov	ax,3
	int	10h		;restore text mode
;
; Exit to DOS.
;
	mov	ah,4ch
	int	21h
Start	endp
;
; Subroutine to draw a text string left-to-right in a linear
;  graphics mode (0Dh, 0Eh, 0Fh, 010h, 012h) with 8-dot-wide
;  characters. Background around the pixels that make up the
;  characters is preserved.
; Font used should be pointed to by FontPointer.
;
; Input:
;  AH = color to draw string in
;  BX = row to draw string on
;  CX = column to start string at
;  DS:SI = string to draw
;
;  Forces ALU function to "move".
;  Forces write mode 3.
;
DrawString	proc	near
	push	ax
	push	bx
	push	cx
	push	dx
	push	si
	push	di
	push	bp
	push	ds
;
; Set up set/reset to produce character color, using the readability
; of VGA register to preserve the setting of reserved bits 7-4.
;
	mov	dx,GC_INDEX
	mov	al,GC_SET_RESET
	out	dx,al
	inc	dx
	in	al,dx
	and	al,0f0h
	and	ah,0fh
	or	al,ah
	out	dx,al
;
; Select write mode 3, using the readability of VGA registers
; to leave bits other than the write mode bits unchanged.
;
	mov	dx,GC_INDEX
	mov	al,GC_MODE
	out	dx,al
	inc	dx
	in	al,dx
	or	al,3
	out	dx,al
	mov	dx,VGA_VIDEO_SEGMENT
	mov	es,dx		;point to display memory
;
; Calculate screen address of byte character starts in.
;
	push	ds		;point to BIOS data segment
	sub	dx,dx
	mov	ds,dx
	mov	di,ds:[SCREEN_WIDTH_IN_BYTES]	;retrieve BIOS
						; screen width
	pop	ds
	mov	ax,bx		;row
	mul	di		;calculate offset of start of row
	push	di		;set aside screen width
	mov	di,cx		;set aside the column
	and	cl,0111b	;keep only the column in-byte address
	shr	di,1
	shr	di,1
	shr	di,1		;divide column by 8 to make a byte address
	add	di,ax		;and point to byte
;
; Set up the GC rotation. In write mode 3, this is the rotation
; of CPU data before it is ANDed with the Bit Mask register to
; form the bit mask. Force the ALU function to "move". Uses the
; readability of VGA registers to leave reserved bits unchanged.
;
	mov	dx,GC_INDEX
	mov	al,GC_ROTATE
	out	dx,al
	inc	dx
	in	al,dx
	and	al,0e0h
	or	al,cl
	out	dx,al
;
; Set up BH as bit mask for left half, BL as rotation for right half.
;
	mov	bx,0ffffh
	shr	bh,cl
	neg	cl
	add	cl,8
	shl	bl,cl
;
; Draw all characters, left portion first, then right portion in the
; succeeding byte, using the data rotation to position the character
; across the byte boundary and then using write mode 3 to combine the
; character data with the bit mask to allow the set/reset value (the
; character color) through only for the proper portion (where the
; font bits for the character are 1) of the character for each byte.
; Wherever the font bits for the character are 0, the background
; color is preserved.
; Does not check for case where character is byte-aligned and
; no rotation and only one write is required.
;
; Draw the left portion of each character in the string.
;
	pop	cx		;get back screen width
	push	si
	push	di
	push	bx
;
; Set the bit mask for the left half of the character.
;
	mov	dx,GC_INDEX
	mov	al,GC_BIT_MASK
	mov	ah,bh
	out	dx,ax
LeftHalfLoop:
	lodsb
	and	al,al
	jz	LeftHalfLoopDone
	call	CharacterUp
	inc	di		;point to next character location
	jmp	LeftHalfLoop
LeftHalfLoopDone:
	pop	bx
	pop	di
	pop	si
;
; Draw the right portion of each character in the string.
;
	inc	di		;right portion of each character is across
				; byte boundary
;
; Set the bit mask for the right half of the character.
;
	mov	dx,GC_INDEX
	mov	al,GC_BIT_MASK
	mov	ah,bl
	out	dx,ax
RightHalfLoop:
	lodsb
	and	al,al
	jz	RightHalfLoopDone
	call	CharacterUp
	inc	di		;point to next character location
	jmp	RightHalfLoop
RightHalfLoopDone:
;
	pop	ds
	pop	bp
	pop	di
	pop	si
	pop	dx
	pop	cx
	pop	bx
	pop	ax
	ret
DrawString	endp
;
; Draw a character.
;
; Input:
;  AL = character
;  CX = screen width
;  ES:DI = address to draw character at
;
CharacterUp	proc	near
	push	cx
	push	si
	push	di
	push	ds
;
; Set DS:SI to point to font and ES to point to display memory.
;
	lds	si,[FontPointer]	;point to font
;
; Calculate font address of character.
;
	mov	bl,14		;14 bytes per character
	mul	bl
	add	si,ax		;offset in font segment of character

	mov	bp,FONT_CHARACTER_SIZE
	dec	cx		; -1 because one byte per char
CharacterLoop:
	lodsb			;get character byte
	mov	ah,es:[di]	;load latches
	stosb			;write character byte
;
; Point to next line of character in display memory.
;
	add	di,cx
;
	dec	bp
	jnz	CharacterLoop
;
	pop	ds
	pop	di
	pop	si
	pop	cx
	ret
CharacterUp	endp
;
; Set the pointer to the font to draw from to ES:BP.
;
SelectFont	proc	near
	mov	word ptr [FontPointer],bp	;save pointer
	mov	word ptr [FontPointer+2],es
	ret
SelectFont	endp
;
cseg	ends
	end	start
