Implementing a tilemap in MS-DOS

x86 / x64 programming
Post Reply
Posts: 32
Joined: Thu Apr 22, 2021 9:30 pm

Implementing a tilemap in MS-DOS

Post by puppydrum64 » Tue Oct 05, 2021 9:04 pm

I've created an attempt at a tilemap system for MS-DOS, it seems to be working. Unfortunately since MS-DOS's graphics are all done as a bitmap screen there are no hardware coprocessors handling tile graphics for us. It seems to be very fast (although I haven't tried scrolling yet). If you want to test it out I'll leave the source code below. I went ahead and put everything into one convenient file, this should run if you copy and paste this into a blank Notepad++ document and run it using the "uasm DOS_VGA" script in the F6 menu. Please forgive the wonky formatting, that's not how it looked in Notepad++ which I copied and pasted this from. O.o

Code: Select all

	.model small
	.stack 1024
UserRam BYTE 256 DUP (0)
Metatile_LUT_ToUse  equ UserRam		;word data
MetatileMapToUse	equ UserRam+2	;word data
PixelPlotX			equ UserRam+4	;byte data, drawing cursor X pos
PixelPlotY			equ UserRam+5	;byte data, drawing cursor Y pos
null_tile			byte 8,8
					byte 0,0,0,0
					byte 0,0,0,0
					byte 0,0,0,0
					byte 0,0,0,0
					byte 0,0,0,0
					byte 0,0,0,0
					byte 0,0,0,0
					byte 0,0,0,0
;this tile is completely blank.
flowertile_PAL0CMP  byte 8,8
					byte 0AAh,0AAh,0AAh,0AAh
					byte 0AAh,0AAh,0AAh,0AAh
					byte 0AAh,0A4h,04Ah,0AAh
					byte 0AAh,04Ch,0C4h,0AAh
					byte 0A2h,024h,042h,02Ah
					byte 0AAh,022h,022h,0AAh
					byte 0AAh,0AAh,0AAh,0AAh
					byte 0AAh,0AAh,0AAh,0AAh
;PAL0CMP stands for "Palette 0 Compression," meaning that each byte uses palette 0 and thus
;the top nibble goes unused in each pixel. Therefore two pixels can be encoded per byte 
;and unpacked just before storing into VRAM. 
blueflowertile_PAL0CMP  byte 8,8
					byte 0AAh,0AAh,0AAh,0AAh
					byte 0AAh,0AAh,0AAh,0AAh
					byte 0AAh,0A1h,01Ah,0AAh
					byte 0AAh,019h,091h,0AAh
					byte 0A2h,021h,012h,02Ah
					byte 0AAh,022h,022h,0AAh
					byte 0AAh,0AAh,0AAh,0AAh
					byte 0AAh,0AAh,0AAh,0AAh
grasstile_PAL0CMP	byte 8,8
					byte 0AAh,0AAh,0AAh,0AAh
					byte 0A2h,0A2h,0AAh,0AAh
					byte 0AAh,02Ah,0AAh,0AAh
					byte 0AAh,0AAh,0AAh,0AAh
					byte 0AAh,0AAh,0AAh,0AAh
					byte 0AAh,0AAh,0A2h,0A2h
					byte 0AAh,0AAh,0AAh,02Ah
					byte 0AAh,0AAh,0AAh,0AAh
greentile_PAL0CMP	byte 8,8
					byte 0AAh,0AAh,0AAh,0AAh
					byte 0AAh,0AAh,0AAh,0AAh
					byte 0A2h,0AAh,0AAh,0AAh
					byte 0AAh,0AAh,0AAh,0AAh
					byte 0AAh,0AAh,0AAh,02Ah
					byte 0AAh,0AAh,0AAh,0AAh
					byte 0AAh,0A2h,0AAh,0AAh
					byte 0AAh,0AAh,0AAh,0AAh

TilemapLookupTable00	word null_tile				;tile 0
						word greentile_PAL0CMP		;tile 1
						word grasstile_PAL0CMP  	;tile 2
						word flowertile_PAL0CMP		;tile 3
						word blueflowertile_PAL0CMP ;tile 4
Tilemap	byte 960 DUP(0)
;tilemap ram
;you can make this value smaller or larger, but it must be a multiple of decimal 40.
			;    TL  TR  BL  BR
Metatiles00 byte 00h,00h,00h,00h	;m0: null metatile
			byte 01h,01h,01h,01h	;m1: thin grass metatile
			byte 01h,02h,02h,01h	;m2: medium grass metatile
			byte 04h,03h,03h,04h	;m3: two blue flowers, two red flowers
MetatileMap00	byte 02,02,02,03,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01 ;row 0-1
				byte 01,01,03,02,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01 ;row 2-3
				byte 02,03,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01 ;row 4-5
				byte 01,02,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01 ;row 6-7
				byte 01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01 ;row 8-9
				byte 01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01 ;row A-B
				byte 02,02,02,03,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01 ;row C-D
				byte 01,01,03,02,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01 ;row E-F
				byte 02,03,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01 ;row 10-11
				byte 01,02,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01 ;row 12-13
				byte 01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01 ;row 14-15
				byte 01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01 ;row 16-17
				byte 01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01 ;row 18-19
				; 40 columns, 12 rows of metatiles. 
				; Each entry in this array represents a 16x16 pixel area, 
				; made of 4 8x8 tiles each.
	mov ax,@data
	mov ds,ax
	mov ah,0	;command to change video modes
	mov al,13h	;VGA
	int 10h		;change video mode to VGA
	mov ax,0A000h	;VGA VRAM
	mov es,ax	
;setup the tilemap
	mov si,offset Metatiles00	
	mov word ptr [ds:MetatileMapToUse],si
	mov si,offset MetatileMap00
	call SetupTilemap

;draw the tilemap
	mov ax, offset TilemapLookupTable00
	mov word ptr [ds:Metatile_LUT_ToUse],ax
	mov si, offset Tilemap
	call Draw_VGA_Tilemap
	mov ax,4C00h
	int 21h
	;end of main program
	;input: DH = row, DL = column (measured in 8x8 pixel squares.)
	;DH ranges from 0 to 39 (these are decimal)
	;DL ranges from 0 to 24 (these are decimal)
	;outputs to DI
	push dx
	push bx
	push ax
	;store x pos.
		mov ah,0
		mov al,dh
		shl AX,1
		shl AX,1
		shl AX,1
		mov di,ax
		mov ax,80
			shl ax,1
			shl ax,1
			shl ax,1
			shl ax,1
			shl ax,1
		mov dh,0		;set DX = 00DL
		mul dx			
		;only seems to work as a 32-bit product
		;even though the product never exceeds 16 bits.
		add di,ax
	pop ax
	pop bx
	pop dx
	;input: ds:si = source bitmap
	;ch = bitmap height in pixels
	;cl = bitmap width in pixels.
	push di			;now bp+2 = cl and bp+3 = ch
		mov ch,byte ptr[ds:PixelPlotY]
		dec ch
		jnz DrawBitmap_VGA_XAgain
	pop di
	add di,320		;one row of pixels = screen width
	dec cl
	jnz DrawBitmap_VGA_YAgain
	;This is for the "Palette 0 Compression" algorithm I created.
	;It's very very simple: Each byte stores two pixels. Since the
	;first 16 colors have a leading zero nibble, we can store twice as much info in
	;one byte. The bytes are unpacked at runtime and stored in pairs.
	;This does limit us to 16 colors but if we were doing that anyway it's ok
	;NORMAL: 03,03,03,03,03,03,03,03
	;PAL0CMP: 33,33,33,33
	;unfortunately this does not work with graphics that have odd dimensions.
	push di
		mov ch,byte ptr[ds:PixelPlotY]
		lodsb		;get compressed byte in al
		mov ah,al	;copy it to ah
		and ah,00001111b	;chop off the top nibble
		and al,11110000b	;chop off the bottom nibble
		;it's easier to do this little-endian since stosb only works with al
			shr al,1
			shr al,1
			shr al,1
			shr al,1			;right shift four times
		stosb					;store al into vram
		xchg ah,al				;swap to get the next pixel
		stosb					;store al (old ah) into vram
		dec ch
		dec ch
		jnz DrawBitmap_VGA_XAgain_Palette0
	pop di
	add di,320
	dec cl
	jnz DrawBitmap_VGA_YAgain_Palette0
	mov dx,0	;reset drawing cursors to top left of screen
	lodsb			;get the next tile index
	push si
	push bx
		mov ah,0
		mov bx,word ptr [ds:Metatile_LUT_ToUse]
		;in a later build pull this from ram, to allow for more
		;lookup tables than just one.
		call XLATW
		;returns the offset of the tile's bitmap data in ax
		mov si,ax
		call LocateVRAM	;adjust DI according to DX.
		mov cl,al
		mov ch,al
		mov word ptr [ds:PixelPlotX],cx
		call DrawBitmap_VGA_Palette0
	pop bx
	pop si
	inc dh
	cmp dh,40
	jb Draw_VGA_Tilemap_NextColumn
	mov dh,0
	inc dl
	cmp dl,18h
	jb Draw_VGA_Tilemap_NextRow
	;ds:si = metatiles list to load
	;es:di = Tilemap
	push es
		mov dx,seg Tilemap
		mov es,dx
		mov di,offset Tilemap
		mov dx,0
		mov cx,18h		;max screen height (in 8x8 squares)
			push di
				lodsb	;get metatile index
				push si
					mov si,word ptr [ds:MetatileMapToUse]
					shl al,1
					shl al,1	;each row is 4 bytes.
					;once you've selected a row, go ahead and copy to tilemap ram.
					mov bx,0
					mov bl,al
					lea ax,[bx+si]
					mov si,ax	;now si is pointing to the beginning of the desired row.
						add di,40-2	;get to next row of tilemap just underneath where we were.
				pop si
			pop di
			inc di
			inc di
				inc dx
				inc dx			;dx mimics di for the purposes of telling when we're done.
			cmp dx,40
			jb repeat_tilemap_nextSquare
			mov dx,0
			add di,40			;skip a row so that we don't draw over top of what's already there
			loop repeat_tilemap_nextRow
	pop es

;input: ds:bx = the table you wish to look up
;AL= the raw index into this table as if it were byte data.
;So don't left shift prior to calling this.
;AH is destroyed.
		SHL AL,1
		mov ah,al	;copy AL to AH
		XLAT		;returns low byte in al
		inc ah
		xchg al,ah	;XLAT only works with AL
		XLAT		;returns high byte in al (old ah)
		xchg al,ah
		;now the word is loaded into ax, big-endian.
	end start	;EOF

Post Reply

Return to “8086 Assembly Programming”