====== D.Y.S.P. using $d017 ====== ===== Introduction ===== D.Y.S.P. stands for **D**ifferent **Y** **S**prite **P**ositioning. Normally only used to refer to a sprite movement in the side borders. At least that's how I understand it, moving sprites around without opening the borders isn't really VIC-magic, now is it? Anyway, a DYSP can be done in many ways, the way I'm describing below is probably the easiest way, since it keeps the timing for each raster line constant by 'cheating' with $d017 manipulation. {{:base:d017-dysp.png|Screen of the DYSP in action}} ==== Assembling the code ==== The code below is in [[http://tass64.sourceforge.net/|64tass]] format, and can be assembled using: 64tass -C -a -o dysp.prg dysp.asm... assuming one would copy and paste the code into a file called 'dysp.asm'. Update: The code is now hosted on [[https://github.com/Compyx/dysp-d017|GitHub]], just clone the repo and run make to assemble, or make x64 to assemble and run. ==== Sprite stretching theory ==== I'm not going into detail on how sprite stretching works, there's an [[magazines:chacking5#the_demo_cornerstretching_sprites|excellent article]] by Pasi Ojala on sprite stretching, explaining this trick in great detail. ===== The Problem ===== Moving sprites into the side border is fairly easy, but requires cycle exact timing. There are plenty of articles here which explain this. Using a constant Y-position for the sprites makes the timing code easy, the problem arises when we want to have variable Y-positions for each sprite, this screws with our timing. There's a multitude of ways to solve this, I'll be describing what I consider the easiest and 'cheating' way of doing it. We use sprite stretching via $d017 to keep our timing constant. ===== The Solution ===== Using the $d017 sprite stretching technique, we can make our sprites arbitrarily high, repeating each line of a sprite for as long as we like. This keeps our border opening routine simple. Here's the routine, basically an adjusted FLD with side border opening and sprite stretching. The interesting part is the d017_table access, this allows use to stretch (or not) each line of each sprite: ; The $d017 stretcher ; ; Basically an FLD with $d017 manipulation and open borders: we use the FLD ; effect to inhibit bad lines, giving us 63 cycles on each rasterline, of which ; a lot of cycles get eaten by the sprites ; ; At each line we set Y-stretch to false, immediately followed by true, which ; causes a line of a sprite to be stretched indefinitely. When we set the ; Y-stretch to false at a line (after the initial false condition), we allow ; the VIC to update its position in the sprite matrix, thus displaying a line ; of a sprite ; ; This loop can be unrolled and optimized to allow for raster splits, if ; desired stretcher ldy #0 ldx #0 - sty $d017 ; set Y-stretch to false lda d017_table,x ; set Y-stretch to true for selected sprites sta $d017 lda d011_table,x bit $ea nop nop dec $d016 ; open side border and do FLD to inhibit sta $d011 ; bad lines inc $d016 inx cpx #64 bne - rts ==== Stretching the first and last sprite line ==== The trick to variable Y-positioning is to stretch the first line of a sprite until we reach the Y-position where we want the sprite to display, then displaying 19 lines of the sprite (not stretching the sprite(s)), and then stretching the last line of the sprite until the loop ends. Suppose we would want to display three sprites, each one one pixel lower than the next. 'A' means sprite 0, 'B' sprite 1 and so on. A[xx] means which line of the sprite we display, 00 is the empty sprite line which we use to stretch until we display the actual sprite data (01-19), line 20 is the empty line we use to complete the loop. Sprites $d017 Display --------------- -------------- --------------------- A00 B00 C00 %11111111 $ff (nothing, we stretch the first, empty, line A01 B00 C00 %11111110 $fe AAA A02 B01 C00 %11111100 $fc AAA BBB A03 B02 C01 %11111000 $f8 AAA BBB CCC A04 B03 C02 %11111000 $f8 AAA BBB CCC ... ... ... %11111000 $f8 AAA BBB CCC A20 B19 C18 %11111001 $f9 BBB CCC A20 B20 C19 %11111011 $fb CCC A20 B20 C20 %11111111 $ff (nothing, we stretch the last, empty, line ==== Generating the $d017 values ==== Generating the correct $d017 value is quite simple, but takes some cycles. * fill $d017 table with $ff (all sprites are stretched) * for each SPRITE: - get Y-position of SPRITE - mask out bits in $d017 table, starting at Y-position, for 19 lines * next SPRITE In pseudo-code, this would look like this: ; fill $d017 table with $ff ldx #0 lda #$ff - sta $1000,x inx cpx #64 bne - ; render sprite 0 ldy sprite0_y ldx #0 - lda $1000,x and #%11111110 ; don't stretch sprite 0 sta $1000,x iny inx cpx #19 ; repeat 19 times bne - ; more code ... ; render sprite 7 ldy sprite7_y ldx #0 - lda $1000,x and #%01111111 ; don't stretch sprite 7 sta $1000,x iny inx cpx #19 bne - ==== Limitations ==== This of course means we can only use 19 pixels high sprites, lines 0 and 20 are cleared so we don't see the stretched sprite data. ===== The Code ===== Putting it all together, we end up with the code below. I use two sinus tables for the X-movement, giving us 344 pixels total movement (actually a little less to avoid the DMA sprite access bug in the right border) and a single sinus for the Y movement. Again, this code is not up to (my) demo standards: change the border color to non-black and you'll see the border-opening bugs at the start of the routine. ; vim: set et ts=8 sw=8 sts=8 syntax=64tass : ; ; Simple, 'cheated', sideborder D.Y.S.P., using $d017 stretching to allow for ; different Y-positions of each sprite. Since we're using $d017 to stretch the ; first and last line of the sprite over the whole area, we can only use 19 ; lines high sprites, the first and last line must be clear so we won't see ; the $d017 stretcher effect. ; ; With some extra $d011 manipulation, this routine can be used for a ; line cruncher. My line cruncher part in 'Doolittle/Focus' uses this technique ; to display a swinging logo during the line crunching. ; ; 2016-04-22 ; Music, comment out, or adjust to taste music_sid ="/home/compyx/c64/HVSC/MUSICIANS/J/JCH/Training.sid" music_init = $1000 music_play = $1003 ; BASIC SYS line * = $0801 .word (+), 2016 .null $9e, ^start + .word 0 ; Entry point: generate sprite and set up IRQ start jsr $fda3 jsr $fd15 sei jsr create_sprite ldx #7 - lda #$0340/64 sta $07f8,x lda spr_colors,x sta $d027,x dex bpl - lda #0 jsr music_init lda #$35 sta $01 lda #$7f sta $dc0d sta $dd0d lda #0 sta $3fff sta $dc0e lda #$01 sta $d01a lda #$1b sta $d011 lda #$2d ldx #irq1 sta $d012 stx $fffe sty $ffff ldx #break stx $fffa sty $fffb stx $fffc sty $fffd bit $dc0d bit $dd0d inc $d019 cli jmp * ; make sure timing loops don't cross page boundaries .align 256 ; 'double IRQ' technique to stabilize raster irq1 pha txa pha tya pha lda #$2e ldx #irq2 sta $d012 stx $fffe sty $ffff lda #1 inc $d019 tsx cli nop nop nop nop nop nop nop nop nop nop nop irq2 txs ldx #8 - dex bne - bit $ea lda $d012 cmp $d012 beq + + ; stable raster here ; set sprite positions lda #$32 ; constant, the Y-movement is done with $d017 magic sta $d001 sta $d003 sta $d005 sta $d007 sta $d009 sta $d00b sta $d00d sta $d00f x0 lda #$00 sta $d000 x1 lda #$18 sta $d002 x2 lda #$30 sta $d004 x3 lda #$48 sta $d006 x4 lda #$60 sta $d008 x5 lda #$78 sta $d00a x6 lda #$90 sta $d00c x7 lda #$a8 sta $d00e xmsb lda #$00 sta $d010 lda #$00 sta $d01c sta $d01d lda #$ff sta $d015 ldx #09 - dex bne - nop nop nop jsr stretcher lda #$1b sta $d011 lda #0 sta $d015 sta $d021 dec $d020 jsr clear_d017_table dec $d020 jsr sinus_x jsr sinus_y dec $d020 jsr update_d017_table lda #0 sta $d020 lda #$f9 ldx #irq3 sta $d012 stx $fffe sty $ffff lda #1 sta $d019 pla tay pla tax pla break rti irq3 pha txa pha tya pha ldx #3 ; open top/bottom borders to allow us to open the - dex ; side borders earlier in the $d017 stretcher bne - stx $d011 ldx #40 - dex bne - lda #$1b sta $d011 dec $d020 jsr music_play lda #0 sta $d020 lda #$2d ldx #irq1 sta $d012 stx $fffe sty $ffff lda #1 sta $d019 pla tay pla tax pla rti ; Create a single sprite at $0340 create_sprite ldx #0 - lda sprite,x sta $0340,x inx cpx #63 bne - rts ; Clear the $d017 'stretcher' table by storing $ff in it ; ; We later mask out bits in this table with the $d017 update routine clear_d017_table lda #$ff ldx #$3f - sta d017_table,x dex bpl - rts ; Update the $d017 'stretcher' table ; ; This is what creates the D.Y.S.P. effect ; ; For each sprite, we get its Y-position and mask out the proper bits in the ; $d017 table. We only mask out bits for 19 lines, line 0 and line 20 are ; always stretched to keep the timing in the loop constant update_d017_table ; sprite 0 ldx siny_table + 0 ldy #18 - lda d017_table,x and #%11111110 sta d017_table,x inx dey bpl - ; sprite 1 ldx siny_table + 1 ldy #18 - lda d017_table,x and #%11111101 sta d017_table,x inx dey bpl - ldx siny_table + 2 ldy #18 - lda d017_table,x and #%11111011 sta d017_table,x inx dey bpl - ldx siny_table + 3 ldy #18 - lda d017_table,x and #%11110111 sta d017_table,x inx dey bpl - ldx siny_table + 4 ldy #18 - lda d017_table,x and #%11101111 sta d017_table,x inx dey bpl - ldx siny_table + 5 ldy #18 - lda d017_table,x and #%11011111 sta d017_table,x inx dey bpl - ldx siny_table + 6 ldy #18 - lda d017_table,x and #%10111111 sta d017_table,x inx dey bpl - ; sprite 7 ldx siny_table + 7 ldy #18 - lda d017_table,x and #%01111111 sta d017_table,x inx dey bpl - rts ; X-movement parameters, two sinus tables added together sinx_idx1 .byte 0 sinx_idx2 .byte 64 sinx_adc1 .byte 8 sinx_adc2 .byte 5 sinx_spd1 .byte $fe sinx_spd2 .byte $03 xmsb_temp .byte 0 ; Calculate the X-movement of the sprites sinus_x lda #0 sta xmsb_temp ; temporary storage for $d010 ldx sinx_idx1 ldy sinx_idx2 lda sinus256,x clc adc sinus88,y sta x0 + 1 bcc + lda xmsb_temp ora #1 sta xmsb_temp + txa clc adc sinx_adc1 tax tya clc adc sinx_adc2 tay lda sinus256,x clc adc sinus88,y sta x1 + 1 bcc + lda xmsb_temp ora #2 sta xmsb_temp + txa clc adc sinx_adc1 tax tya clc adc sinx_adc2 tay lda sinus256,x clc adc sinus88,y sta x2 + 1 bcc + lda xmsb_temp ora #4 sta xmsb_temp + txa clc adc sinx_adc1 tax tya clc adc sinx_adc2 tay lda sinus256,x clc adc sinus88,y sta x3 + 1 bcc + lda xmsb_temp ora #8 sta xmsb_temp + txa clc adc sinx_adc1 tax tya clc adc sinx_adc2 tay lda sinus256,x clc adc sinus88,y sta x4 + 1 bcc + lda xmsb_temp ora #16 sta xmsb_temp + txa clc adc sinx_adc1 tax tya clc adc sinx_adc2 tay lda sinus256,x clc adc sinus88,y sta x5 + 1 bcc + lda xmsb_temp ora #32 sta xmsb_temp + txa clc adc sinx_adc1 tax tya clc adc sinx_adc2 tay lda sinus256,x clc adc sinus88,y sta x6 + 1 bcc + lda xmsb_temp ora #64 sta xmsb_temp + txa clc adc sinx_adc1 tax tya clc adc sinx_adc2 tay lda sinus256,x clc adc sinus88,y sta x7 + 1 bcc + lda xmsb_temp ora #128 sta xmsb_temp + lda xmsb_temp sta xmsb + 1 lda sinx_idx1 clc adc sinx_spd1 sta sinx_idx1 lda sinx_idx2 clc adc sinx_spd2 sta sinx_idx2 rts ; Y-movement parameters, a single sinus, for now siny_table .fill 8, 0 siny_idx .byte 0 siny_adc .byte 16 siny_spd .byte 2 sinus_y ldy #0 ldx siny_idx - lda sinus40,x clc adc #1 sta siny_table,y txa clc adc siny_adc tax iny cpy #8 bne - lda siny_idx clc adc siny_spd sta siny_idx rts ; Colors for the sprites spr_colors .byte 1, 7, 13, 15, 14, 4, 6, 9 ; Example sprite sprite .byte 0, 0, 0 .byte %00000000, %00000000, %00000000 .byte %00000000, %11111111, %00000000 .byte %00000111, %11111111, %11100000 .byte %00011111, %11000001, %11111000 .byte %00111111, %10000000, %11100000 .byte %01111111, %00000000, %00000000 .byte %01111111, %00000000, %00000000 .byte %11111111, %00000000, %00000000 .byte %11111111, %00000000, %00000000 .byte %11111111, %00000000, %00000000 .byte %11111111, %00000000, %00000000 .byte %11111111, %00000000, %00000000 .byte %01111111, %00000000, %00000000 .byte %01111111, %00000000, %11100000 .byte %00111111, %10000000, %11111000 .byte %00011111, %11000011, %11111000 .byte %00000111, %11111111, %11100000 .byte %00000000, %11111111, %00000000 .byte %00000000, %00000000, %00000000 .byte 0, 0, 0 ; make sure we don't cross a page in the stretcher or its data .align 256 ; The $d017 stretcher ; ; Basically an FLD with $d017 manipulation and open borders: we use the FLD ; effect to inhibit bad lines, giving us 63 cycles on each rasterline, of which ; a lot of cycles get eaten by the sprites ; ; At each line we set Y-stretch to false, immediately followed by true, which ; causes a line of a sprite to be stretched indefinately. When we set the ; Y-stretch to false at a line (after the initial false condition), we allow ; the VIC to update its position in the sprite matrix, thus displaying a line ; of a sprite ; ; This loop can be unrolled and optimized to allow for raster splits, if ; desired stretcher ldy #0 ldx #0 - sty $d017 ; set Y-stretch to false lda d017_table,x ; set Y-stretch to true for selected sprites sta $d017 lda d011_table + 0,x bit $ea nop nop dec $d016 ; open side border and do FLD to inhibit sta $d011 ; bad lines inc $d016 inx cpx #64 bne - rts ; Table with values for $d017 in the stretcher d017_table .fill 64, $ff ; Values for $d011 in the stretcher d011_table - = range(0, 64, 1) .byte <(-) & 7 | $10 ; Don't overwrite music with code/data .cerror * > $0fff, "code section too long" ; Link music * = $1000 .binary music_sid, $7e * = $2000 ; Sinus for X-movement sinus256 .byte 127.5 + 128 * sin(range(256) * rad(360.0/256)) sinus88 .byte 42.5 + 43 * sin(range(256) * rad(360.0/256)) ; Sinus for Y-movement sinus40 .byte 19.5 + 20 * sin(range(256) * rad(360.0/256))