User Tools

Site Tools


base:dysp_d017

D.Y.S.P. using $d017

Introduction

D.Y.S.P. stands for Different Y Sprite Positioning. 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.

Screen of the DYSP in action

Assembling the code

The code below is in 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 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 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:
    1. get Y-position of SPRITE
    2. 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
        ldy #>irq1
        sta $d012
        stx $fffe
        sty $ffff
        ldx #<break
        ldy #>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
        ldy #>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
        ldy #>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
        ldy #>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))
base/dysp_d017.txt · Last modified: 2016-04-26 13:52 by compyx