.EQU SPRITE_HEIGHT 16 .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_FS 28 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 ; ; process spilled bits and right half of sprite data ; ; 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 ; 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 ; write spilled bits 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_S_FS 24 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 ; load 2nd word of background data LOAD UD_S_BGDATA INC 4 DUP STORE UD_S_BGDATA LOADI STOREI ; and write it to vmem DROP ; if pixel shift is zero, no 3rd word LOAD UD_S_PXS CBRANCH.Z UD_S_L2 ; load 3rd word of background data LOADCP FB_IO LOAD UD_S_BGDATA INC 4 DUP STORE UD_S_BGDATA 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