• Please review our updated Terms and Rules here

Drawing Pixel Perfect Triangles (Part 3: X86 Assembler)

neilobremski

Experienced Member
Joined
Oct 9, 2016
Messages
55
Location
Seattle, USA
In this final, third part blog post I present to you finished 16-bit X86 assembler code for CGA mode 4 (320x200x4) written as a DEBUG script [SUP][1][/SUP]. This will give you a 556 byte routine starting at address 1300h.

Code:
a 1300
; ----------------------------------------------------------------------------
; TRIFILL (AH,AL) - (BH,BL) - (DH,DL), CH			:TRIFILL
;
;	[params]	[stepping]	[drawing]
;
;     / AH = X1		/ StepA		/ XBeg
; AX /_ AL = Y1		/ StepB		/ XEnd
;
;     / BH = X2		/ XDistA	/ (preserve)
; BX /_ BL = Y2		/ YDistA	/ (preserve)
;
;     / CH = Color	/ ErrA		/ PixAvail
; CX /_ CL = (ignore)	/ ErrB		/ Length
;
;     / DH = X3		/ XDistB	/ AdjSideA
; DX /_ DL = Y3		/ YDistB	/ AdjSideB
;
;     / 80 = (ignore)	/ Color		/ Color
; SI /_ 08 = (ignore)	/ Scanlines	/ Scanlines
;
;     / 80 = (preserve)	/ XSideA	/ XSideA
; BP /_ 08 = (preserve)	/ XSideB	/ XSideB
;
; DI =  DI = Pointer to beginning of current scanline
;
; IMPORTANT: All general registers but SP & BP will be modified by this routine!
;
PUSH	BP	; 300 save stack frame
CLD		; 301 always write forward
XOR	CL, CL	; 302
MOV	SI, CX	; 304 save color to SI
XOR	CH, CH	; 306
PUSH	CX	; 308 end of triangle marker (0)
		;
		; SORT POINTS (TOP TO BOTTOM)
		;
CMP	AL, BL	; 309 compare Y1 to Y2				:TRI_SORT1
JBE	130E	; 30B jump next instruction >TRI_SORT2
XCHG	AX, BX	; 30D swap P1 and P2
CMP	AL, DL	; 30E compare Y1 to Y3				:TRI_SORT2
JBE	1313	; 310 jump next instruction >TRI_SORT3
XCHG	AX, DX	; 312 swap P1 and P3
CMP	BL, DL	; 313 compare Y2 to Y3				:TRI_SORT3
JBE	1319	; 315 jump next instruction >TRI_SORTDONE
XCHG	BX, DX	; 317 swap P2 and P3
		;						:TRI_SORTDONE
		; INITIAL SCANLINE (DI)
		;
MOV	BP, AX	; 319 save AX since we're going to mangle it	:TRI_DI
XOR	DI, DI	; 31B
XOR	AH, AH	; 31D clear AH
TEST	AL, 01	; 31F is this an odd scanline?
JZ	1327	; 321 jump next instruction >TRI_DI_OFFS
ADD	DI, 2000; 323 add 0x2000 for odd scanline
AND	AL, FE	; 327 clear bit 0				:TRI_DI_OFFS
SHL	AX, 1	; 329
SHL	AX, 1	; 32B
SHL	AX, 1	; 32D
ADD	DI, AX	; 32F add *64 to DI
SHL	AX, 1	; 331
SHL	AX, 1	; 333
ADD	DI, AX	; 335 add *16 to DI (*64 + *16 = *80 bytes per line)
MOV	AX, BP	; 337 restore AX
		;
		; SLOPE 2to3 (SIDE A, BOTTOM HALF)
		;
MOV	CX, DX	; 339						:TRI_2TO3_PREP
SUB	CL, BL	; 33B
JZ	137B	; 33D if (no bottom half) goto >TRI_1TO3_PREP
INC	CL	; 33F YDist2to3 = Y3 - Y2 + 1
SUB	CH, BH	; 341
JNC	1360	; 343 >TRI_2TO3_RIGHT
NEG	CH	; 345						:TRI_2TO3_LEFT
MOV	BP, C529; 347 [SUB BP, AX]
MOV [1524], BP	; 34A $TRI_PRESTEP
MOV	BP, F588; 34E [MOV CH, DH] XBeg = XSideA
PUSH	BP	; 351
MOV	BP, EE38; 352 [CMP DH, CH] if (XSideA <= XBeg)
PUSH	BP	; 355
MOV	BP, C6FE; 356 [INC DH]
PUSH	BP	; 359
MOV	BP, E628; 35A [SUB DH, AH]
PUSH	BP	; 35D 
JMP	1377	; 35E >TRI_2TO3_DONE
		;
MOV	BP, C501; 360 [ADD BP, AX]				:TRI_2TO3_RIGHT
MOV [1524], BP	; 363 $TRI_PRESTEP
MOV	BP, F188; 367 [MOV CL, DH] XEnd = XSideA
PUSH	BP	; 36A
MOV	BP, F138; 36B [CMP CL, DH] if (XEnd <= XSideA)
PUSH	BP	; 36E
MOV	BP, CEFE; 36F [DEC DH]
PUSH	BP	; 372
MOV	BP, E600; 373 [ADD DH, AH]
PUSH	BP	; 376
INC	CH	; 377 XDist2to3 = X3 - X2 + 1			:TRI_2TO3_DONE
PUSH	BX	; 379 save X2,.. for bottom half
PUSH	CX	; 37A save XDist2to3,YDist2to3 for bottom half
		;
		; SLOPE 1to3 (SIDE B, LONG SIDE)
		;
SUB	DL, AL	; 37B						:TRI_1TO3_PREP
INC	DL	; 37D YDist1to3 = Y3 - Y1 + 1
SUB	DH, AH	; 37F
JNC	13A3	; 381 >TRI_1TO3_RIGHT
NEG	DH	; 383						:TRI_1TO3_LEFT
MOV	BP, C228; 385 [SUB DL, AL]
MOV [143C], BP	; 388 $TRI_CODE_ADDB
MOV	BP, C2FE; 38C [INC DL]
MOV [1450], BP	; 38F $TRI_CODE_ADJB
MOV	BP, EA38; 393 [CMP DL, CH] if (XSideB <= XBeg)
MOV [1452], BP	; 396 $TRI_CODE_CMPB
MOV	BP, D588; 39A [MOV CH, DL] XBeg = XSideB
MOV [1456], BP	; 39D $TRI_CODE_USEB
JMP	13BF	; 3A1 >TRI_1TO3_DONE
		;
MOV	BP, C200; 3A3 [ADD DL, AL]				:TRI_1TO3_RIGHT
MOV [143C], BP	; 3A6 $TRI_CODE_ADDB
MOV	BP, CAFE; 3AA [DEC DL]
MOV [1450], BP	; 3AD $TRI_CODE_ADJB
MOV	BP, D138; 3B1 [CMP CL, DL] if (XEnd <= XSideB)
MOV [1452], BP	; 3B4 $TRI_CODE_CMPB
MOV	BP, D188; 3B8 [MOV CL, DL] XEnd = XSideB
MOV [1456], BP	; 3BB $TRI_CODE_USEB
INC	DH	; 3BF XDist1to3 = X3 - X1 + 1			:TRI_1TO3_DONE
		;
		; SLOPE 1to2 (SIDE A, TOP HALF)
		;
MOV	CH, AH	; 3C1						:TRI_1TO2_PREP
MOV	CL, AH	; 3C3
MOV	BP, CX	; 3C5 XSideA,XSideB = X1,X1
SUB	BL, AL	; 3C7
INC	BL	; 3C9 YDist1to2 = Y2 - Y1 + 1
SUB	BH, AH	; 3CB
JNC	13EF	; 3CD >TRI_1TO2_RIGHT
NEG	BH	; 3CF						:TRI_1TO2_LEFT
MOV	CX, E628; 3D1 [SUB DH, AH]
MOV [143A], CX	; 3D4 $TRI_CODE_ADDA
MOV	CX, C6FE; 3D8 [INC DH]
MOV [1444], CX	; 3DB $TRI_CODE_ADJA
MOV	CX, EE38; 3DF [CMP DH, CH] if (XSideA <= XBeg)
MOV [1446], CX	; 3E2 $TRI_CODE_CMPA
MOV	CX, F588; 3E6 [MOV CH, DH] XBeg = XSideA
MOV [144A], CX	; 3E9 $TRI_CODE_USEA
JMP	140B	; 3ED >TRI_1TO2_DONE
		;
MOV	CX, E600; 3EF [ADD DH, AH]				:TRI_1TO2_RIGHT
MOV [143A], CX	; 3F2 $TRI_CODE_ADDA
MOV	CX, CEFE; 3F6 [DEC DH]
MOV [1444], CX	; 3F9 $TRI_CODE_ADJA
MOV	CX, F138; 3FD [CMP CL, DH] if (XEnd <= XSideA)
MOV [1446], CX	; 400 $TRI_CODE_CMPA
MOV	CX, F188; 404 [MOV CL, DH] XEnd = XSideA
MOV [144A], CX	; 407 $TRI_CODE_USEA
INC	BH	; 40B XDist1to2 = X2 - X1 + 1			:TRI_1TO2_DONE
		;
		; TRIFILL TOP HALF
		;
MOV	CL, BL	; 40D						:TRIFILL_TOP
XOR	CH, CH	; 40F ErrA = 0
OR	SI, CX	; 411 Scanlines (LSB) = YDistA
XOR	CL, CL	; 413 ErrB = 0
		; --------------------------------------------------------------
		; TRIFILL MAIN LOOP
		;
XOR	AX, AX	; 415						:TRIFILL_LOOP
		;
		; COMPUTE STEPS
		;
MOV	AL, CH	; 417
ADD	AL, BH	; 419
ADC	AH, AH	; 41B
DIV	BL	; 41D ErrA /= YDistB
MOV	CH, AH	; 41F ErrA = AH
XOR 	AH, AH	; 421
XCHG	CL, AL	; 423 ErrB <swap> StepA
ADD	AL, DH	; 425 ErrB += XDistB
ADC	AH, AH	; 427
DIV	DL	; 429 ErrB /= YDistB
XCHG	AH, CL	; 42B ErrB <swap> StepA
		;
PUSH	CX	; 42D
PUSH	DX	; 42E
PUSH	DI	; 42F
		;
		; STEP SIDES (BP = new XSideA, XSideB)
		;
MOV	DX, BP	; 430
MOV	CX, BP	; 432
CMP	CH, CL	; 434 XBeg vs XEnd
JB	143A	; 436 >TRI_CODE_ADDA
XCHG	CL, CH	; 438 XBeg,XEnd = Sort(XSideA, XSideB)
ADD	DH, AH	; 43A 						:TRI_CODE_ADDA
ADD	DL, AL	; 43C 						:TRI_CODE_ADDB
MOV	BP, DX	; 43E XSideA += StepA, XSideB += StepB
		;
		; USE SIDE A TO EXPAND SEGMENT
		;
OR	AH, AH	; 440						:TRI_USE_SIDEA
JZ	144C	; 442 >TRI_USE_SIDEB
DEC	DH	; 444 AdjSideA = XSideA - 1			:TRI_CODE_ADJA
CMP	CL, DH	; 446						:TRI_CODE_CMPA
JA	144C	; 448 >TRI_USE_SIDEB
MOV	CL, DH	; 44A						:TRI_CODE_USEA
		;
		; USE SIDE B TO EXPAND SEGMENT
		;
OR	AL, AL	; 44C						:TRI_USE_SIDEB
JZ	1458	; 44E >TRI_DRAW_SEGLEN
DEC	DL	; 450 AdjSideB = XSideB - 1			:TRI_CODE_ADJB
CMP	AL, DL	; 452						:TRI_CODE_CMPB
JA	1458	; 454 >TRI_DRAW_SEGLEN
MOV	AL, DL	; 456						:TRI_CODE_USEB
		;
		; CALCULATE SEGMENT LENGTH
		;
SUB	CL, CH	; 458						:TRI_DRAW_SEGLEN
INC	CL	; 45A Length = XEnd - XBeg + 1
		;
		; ADJUST DI TO FIRST PIXEL
		;
MOV	AL, CH	; 45C
XOR	AH, AH	; 45E
SHR	AL, 1	; 460
SHR	AL, 1	; 462
ADD	DI, AX	; 464 DI += XBeg / 4
MOV	DH, FF	; 466 DH -> mask
AND	CH, 3	; 468
NEG	CH	; 46B
ADD	CH, 4	; 46D PixAvail = 4 - (XBeg % 4)
CMP	CH, CL	; 470
JB	148E	; 472 if (PixAvail > Length) goto >TRI_DRAW_BEGSEG
		;
		; DRAW SCAN LINE SEGMENT
		;
SHL	CL, 1	; 474
SHR	DH, CL	; 476
MOV	CL, CH	; 478
SHL	CL, 1	; 47A
ROL	DH, CL	; 47C
MOV	AX, SI	; 47E
MOV AL, ES:[DI]	; 480
AND	AL, DH	; 483
NOT	DH	; 485
AND	AH, DH	; 487
OR	AL, AH	; 489
STOSB		; 48B store byte (DI is advanced but that is ignored)
JMP	14D1	; 48C goto >TRI_NEXT_LINE
		;
SUB	CL, CH	; 48E Length -= PixAvail			:TRI_DRAW_BEGSEG
SHL	CH, 1	; 490
XCHG	CL, CH	; 492 save swap
SHL	DH, CL	; 494
XCHG	CL, CH	; 496 restore swap
MOV	AX, SI	; 498
MOV AL, ES:[DI]	; 49A
AND	AL, DH	; 49D
NOT	DH	; 49F
AND	AH, DH	; 4A1
OR	AL, AH	; 4A3
STOSB		; 4A5
		;
MOV	DH, CL	; 4A6						:TRI_DRAW_MIDSEG
SHR	CL, 1	; 4A8
SHR	CL, 1	; 4AA
JZ	14B6	; 4AC if (Length / 4 == 0) goto >TRI_DRAW_ENDSEG
MOV	AX, SI	; 4AE
MOV	AL, AH	; 4B0
XOR	CH, CH	; 4B2
REP	STOSB	; 4B4 let 'er rip!
		;
AND	DH, 03	; 4B6 						:TRI_DRAW_ENDSEG
JZ	14D1	; 4B9 if (Length == 0) goto >TRI_NEXT_LINE
MOV	CL, DH	; 4BB
MOV	DH, FF	; 4BD
SHL	CL, 1	; 4BF
SHR	DH, CL	; 4C1
MOV	AX, SI	; 4C3
MOV AL, ES:[DI]	; 4C5
AND	AL, DH	; 4C8
NOT	DH	; 4CA
AND	AH, DH	; 4CC
OR	AL, AH	; 4CE
STOSB		; 4D0
		;
POP	DI	; 4D1						:TRI_NEXT_LINE
POP	DX	; 4D2
POP	CX	; 4D3
		;
ADD	DI, 2000; 4D4
CMP	DI, 4000; 4D8
JAE	14E8	; 4DC >TRI_ODD2EVEN
DEC	SI	; 4DE
TEST	SI, FF	; 4DF
JZ	14F6	; 4E3 >TRIFILL_BOT
JMP	1415	; 4E5 >TRIFILL_LOOP
SUB	DI, 3FB0; 4E8						:TRI_ODD2EVEN
DEC	SI	; 4EC
TEST	SI, FF	; 4ED
JZ	14F6	; 4F1 >TRIFILL_BOT
JMP	1415	; 4F3 >TRIFILL_LOOP
		; --------------------------------------------------------------
		;
		; TRIANGLE BOTTOM HALF
		;
POP	BX	; 4F6 pop XDist2to3,YDist2to3			:TRIFILL_BOT
OR	BX, BX	; 4F7
JZ	1529	; 4F9 if (end of bottom half) goto >TRIFILL_DONE
POP	AX	; 4FB pop X2,..
XOR	AL, AL	; 4FC
XCHG	AX, BP	; 4FE
XOR	AH, AH	; 4FF
OR	BP, AX	; 501 XSideA,XSideB = X2,XSideB
MOV	AL, BL	; 503
OR	SI, AX	; 505
DEC	SI	; 507 Scanlines (LSB) = YDist2to3 - 1
		;
		; REWRITE SIDE A CODE, BOTTOM HALF
		;
POP	AX	; 508
MOV [143A], AX	; 509 $TRI_CODE_ADDA
POP	AX	; 50C
MOV [1444], AX	; 50D $TRI_CODE_ADJA
POP	AX	; 510
MOV [1446], AX	; 511 $TRI_CODE_CMPA
POP	AX	; 514
MOV [144A], AX	; 515 $TRI_CODE_USEA
		;
		; PRESTEP (first scanline is overlapped between halves)
		;
XOR	AX, AX	; 518
MOV	AL, BH	; 51A
DIV	BL	; 51C
MOV	CH, AH	; 51E
MOV	AH, AL	; 520
XOR	AL, AL	; 522
ADD	BP, AX	; 524						:TRI_PRESTEP
		;
JMP	1415	; 526 >TRIFILL_LOOP
		;
POP	BP	; 529 restore stack frame			:TRIFILL_DONE
RET		; 52A

Footnotes:
[SUP][1][/SUP] There are a few enhancements that I made using comments which allow me to insert and relocate things without screwing up the addressing. That said, the script is usable as is.
 
Back
Top