.EQU SPRITE_HEIGHT 32 ; height in lines .EQU SPRITE_STRIPES 4 ; width in words i.e. 8-pixel stripes .EQU WORDS_PER_LINE 80 .EQU FB_RA $900 .EQU FB_WA $901 .EQU FB_IO $902 .EQU FB_PS $903 ; calculate mask for a word of pixels ; args: word of pixels with four bits per pixel ; returns: value that masks out all pixels that are set CALC_MASK: LOADC $F ; pixel mask C_M_L0: SWAP ; swap mask and pixels value AND.S1.X2Y ; isolate one pixel, keep args CBRANCH.Z C_M_L1 ; if pixel is zero, dont set mask bits OVER ; copy current mask OR ; or into pixels value C_M_L1: SWAP ; swap back, ToS is now mask bits SHL 2 ; shift mask for next pixel to the left SHL 2 DUP CBRANCH.NZ C_M_L0 ; if mask is zero, we are done DROP ; remove mask bits NOT ; invert result RET ; calculate vmem address from coordinates ; args: x,y ; returns: vmem word number CALC_VMEM_ADDR: ; only works if WORDS_PER_LINE is 80 ; and pixels per word is 8 DUP ; y SHL 2 SHL 2 SHL 2 ; * 64 SWAP ; + y SHL 2 SHL 2 ; * 16 ADD SWAP ; word offset = X/8 SHR SHR SHR ADD RET ; put a sprite on screen ; arg: x,y pointer to sprite data .EQU PS_VMEM_ADDR 0 .EQU PS_SPRITE_DATA 4 .EQU PS_SPRITE_LINES 8 .EQU PS_X 12 .EQU PS_Y 16 .EQU PS_SHIFT_C 20 .EQU PS_SPILL 24 .EQU PS_STRIPE_C 28 .EQU PS_FS 32 PUTSPRITE: FPADJ -PS_FS STORE PS_SPRITE_DATA STORE PS_Y STORE PS_X ; calculate vmem address LOAD PS_X LOAD PS_Y LOADCP CALC_VMEM_ADDR CALL STORE PS_VMEM_ADDR LOAD PS_X ; shift count = x mod 8 LOADC 7 AND STORE PS_SHIFT_C LOADC SPRITE_HEIGHT STORE PS_SPRITE_LINES ; loop over each line of the sprite PS_LOOP1: ; set read and write address ; in the vga controller LOADC FB_RA ; read address register LOAD PS_VMEM_ADDR STOREI 1 ; use autoincrement to get to the next register LOAD PS_VMEM_ADDR STOREI DROP LOAD PS_SPRITE_DATA ; address of sprite data DUP INC 4 ; increment pointer STORE PS_SPRITE_DATA ; and store it again LOADI ; load word from orig. address LOADC 0 STORE PS_SPILL ; loop to shift pixel data to right LOAD PS_SHIFT_C ; load shift count PS_LOOP2: DUP ; test it for zero CBRANCH.Z PS_LOOP2_X SWAP ; swap count with pixels ; save the pixel that is shifted out LOADC $F ; mask the four bits AND.S0 ; keep original value on stack BROT ; and move them to MSB BROT BROT SHL 2 SHL 2 ; shift by 28 in total LOAD PS_SPILL ; load spill bits SHR ; shift by four to make space SHR SHR SHR OR ; or with orig value STORE PS_SPILL ; store new value SHR ; shift pixels right SHR ; four bits per pixel SHR SHR SWAP ; swap back, count now ToS DEC 1 BRANCH PS_LOOP2 PS_LOOP2_X: DROP ; remove shift count, shifted pixels now in ToS DUP LOADCP CALC_MASK ; calculate sprite mask for this word CALL LOADCP FB_IO ; address of the i/o register LOADI ; read word from video mem AND ; and word with mask OR ; OR sprite data with original pixels LOADCP FB_IO SWAP STOREI ; store result into i/o reg DROP ; set counter for remaining stripes LOADC SPRITE_STRIPES - 1 STORE PS_STRIPE_C ; ; process spilled bits and next vertical stripe of sprite data ; PS_NEXT_STRIPE: ; put spill bits on stack for later LOAD PS_SPILL LOAD PS_SPRITE_DATA ; address of sprite data DUP INC 4 ; increment pointer STORE PS_SPRITE_DATA ; and store it again LOADI ; load word from orig. address ; reset spill bits LOADC 0 STORE PS_SPILL ; last spill bits are on ToS now ; shift pixel data to right LOAD PS_SHIFT_C ; load shift count PS_LOOP3: ; test it for zero DUP CBRANCH.Z PS_LOOP3_X SWAP ; swap count with pixels ; save the pixel that is shifted out LOADC $F ; mask the four bits AND.S0 ; keep original value on stack BROT ; and move them to MSB BROT BROT SHL 2 SHL 2 ; shift by 28 in total LOAD PS_SPILL ; load spill bits SHR ; shift by four to make space SHR SHR SHR OR ; or with orig value STORE PS_SPILL ; store new value SHR ; shift pixels right SHR ; four bits per pixel SHR SHR SWAP ; swap back, count now ToS DEC 1 BRANCH PS_LOOP3 PS_LOOP3_X: DROP ; remove shift count, shifted pixels now in ToS OR ; or together with spill bits DUP LOADCP CALC_MASK ; calculate sprite mask CALL LOADCP FB_IO ; load original pixels LOADI AND ; and with mask OR ; or together with original pixels LOADCP FB_IO SWAP STOREI DROP LOAD PS_STRIPE_C ; decrement stripe count DEC 1 DUP STORE PS_STRIPE_C CBRANCH.NZ PS_NEXT_STRIPE ; if non-zero, next stripe ; write spilled bits of the last stripe into next vmem word LOAD PS_SPILL ; get spill bits DUP LOADCP CALC_MASK ; calculate sprite mask for spill bits CALL LOADCP FB_IO LOADI ; load next vmem word AND ; apply sprite mask OR ; OR in spill bits LOADCP FB_IO SWAP ; swap pixels and addr STOREI ; write back DROP LOAD PS_SPRITE_LINES ; decrement lines count DEC 1 DUP CBRANCH.Z PS_L_XT ; exit if zero STORE PS_SPRITE_LINES ; prepare next line LOAD PS_VMEM_ADDR LOADC WORDS_PER_LINE ; increment to next screen line ADD STORE PS_VMEM_ADDR BRANCH PS_LOOP1 PS_L_XT: DROP FPADJ PS_FS RET ; undraw a sprite, i.e. draw background data ; over a sprite location ; args: x,y, ptr to background data .EQU UD_S_X 0 .EQU UD_S_Y 4 .EQU UD_S_PXS 8 .EQU UD_S_BGDATA 12 .EQU UD_S_OFFSET 16 .EQU UD_S_BGORIG 20 .EQU UD_STRIPE_C 24 .EQU UD_S_FS 28 UNDRAWSPRITE: FPADJ -UD_S_FS STORE UD_S_BGORIG STORE UD_S_Y STORE UD_S_X ; calculate pixel shift LOAD UD_S_X LOADC $7 AND STORE UD_S_PXS ; calculate vmem offset LOAD UD_S_X LOAD UD_S_Y LOADCP CALC_VMEM_ADDR CALL DUP STORE UD_S_OFFSET ; calculate background data address from offset SHL 2 LOAD UD_S_BGORIG ADD STORE UD_S_BGDATA LOADC SPRITE_HEIGHT ; line count UD_S_L1: ; store vmem offset into write addr reg LOADCP FB_WA LOAD UD_S_OFFSET STOREI 1 ; ugly but fast: reuse addr ; with postincrement to ; get to FB_IO for STOREI below ; load a word of background data LOAD UD_S_BGDATA LOADI ; and write it to vmem STOREI ; reuse addr from STOREI LOADC SPRITE_STRIPES - 1 ; set remaining stripe count STORE UD_STRIPE_C UD_NEXT_STRIPE: ; load next word of background data LOAD UD_S_BGDATA INC 4 DUP STORE UD_S_BGDATA LOADI STOREI ; and write it to vmem ; reuse addr from STOREI LOAD UD_STRIPE_C ; decrease remaining stripe count DEC 1 DUP STORE UD_STRIPE_C CBRANCH.NZ UD_NEXT_STRIPE ; if non-zero, next stripe DROP ; remove addr from STOREI ; if pixel shift is zero, no spill word LOAD UD_S_PXS CBRANCH.Z UD_S_L2 ; load next word of background data LOADCP FB_IO LOAD UD_S_BGDATA INC 4 LOADI STOREI ; and write it to vmem DROP UD_S_L2: LOAD UD_S_OFFSET LOADCP WORDS_PER_LINE ADD DUP STORE UD_S_OFFSET SHL 2 LOAD UD_S_BGORIG ADD STORE UD_S_BGDATA DEC 1 ; decrement counter DUP CBRANCH.NZ UD_S_L1 ; check for zero DROP ; remove counter FPADJ UD_S_FS RET