| |
oziphantom
Registered: Oct 2014 Posts: 490 |
Cycle Excact Timings and moving sprites
What fancy techniques to people have for handling the need for cycle exact timing for effects and then having sprites move over it?
I'm thinking of having side borders partially open in places which then may or may not have a number of sprites over. My current thinking is to have sets of 4 clock burners per sprite with a d015 sprite value per line when then looks up a num sprites per line value table per line that then indexes into a Branch offset table. so
lda SpritesValue,x
tay
lda ValueToNumBitsSetLUT,y
tay
lda BranchOffsetTable,y
sta BranchOffset
BranchOffset = *+1
bne $00
lda $00,x
lda $00,x
lda $00,x
lda $00,x ; I think 4 sprites will be enough
38 col
40 col
check num lines and loop
BranchOffsetTable 01,03,05,07,09 ; can't remember at the moment it if needs to be 1 or 0
but I might be over complicating it... |
|
| |
Mixer
Registered: Apr 2008 Posts: 452 |
63 cycle countdown timer is enough for a simple non-badline setting. With badlines its more complicated, one needs 8 line "loop chunks" or similar way to handle the exception.
Something like this
loop
lda #63
sec
sbc timerlo
sta *+1
lda #$a9
lda #$a9
lda #$a9
lda #$a9
.
..
dec/inc d016
dex
bpl loop
Sprite sorting by y-coordinate helps too.
I think countdown timer as a jitter correction mechanism was used already 1988. The first time I saw it used was in an Upfront demo. |
| |
lft
Registered: Jul 2007 Posts: 369 |
Yep, it is sensible to just measure it in realtime using a timer, and compensate.
Otherwise there's the problem of keeping that table updated as the sprites move around. It's not enough to know how many sprites there are, you need to keep track of which individual sprites are active. E.g. sprites 1+3 will take away just as many cycles as sprites 1+2+3, which is not necessarily the same as sprites 0+2 if your other code ends with a write cycle. |
| |
JackAsser
Registered: Jun 2002 Posts: 2014 |
Quote: Yep, it is sensible to just measure it in realtime using a timer, and compensate.
Otherwise there's the problem of keeping that table updated as the sprites move around. It's not enough to know how many sprites there are, you need to keep track of which individual sprites are active. E.g. sprites 1+3 will take away just as many cycles as sprites 1+2+3, which is not necessarily the same as sprites 0+2 if your other code ends with a write cycle.
I did something similar for the sprite multiplexer on top of 4x4 timing in The Wild Bunch. But there each ball consist of 2x3 sprites so there will only be 16 different timing combinations, instead of 256. I couldn't use a timer since the $d011 FLI-write has to come directly after the sprite fetches, so no time to stabalize using a timer. |
| |
chatGPZ
Registered: Dec 2001 Posts: 11386 |
making the table for that by hand is a zen like experience for all "intuitive" coders =P |
| |
Radiant
Registered: Sep 2004 Posts: 639 |
Quote: I did something similar for the sprite multiplexer on top of 4x4 timing in The Wild Bunch. But there each ball consist of 2x3 sprites so there will only be 16 different timing combinations, instead of 256. I couldn't use a timer since the $d011 FLI-write has to come directly after the sprite fetches, so no time to stabalize using a timer.
Were there any glitches? ;-P |
| |
lft
Registered: Jul 2007 Posts: 369 |
Quoting JackAsserI couldn't use a timer since the $d011 FLI-write has to come directly after the sprite fetches, so no time to stabalize using a timer.
Another idea: FLI is self-stabilising, so one solution would be to narrow the 4x4 area from the left by the expected amount of jitter due to sprite fetches.
Clarifying example: To place 2 moving sprites over a 4x4 routine, you'll get a variable delay due to sprite DMA in the range of 0..7 cycles. Start counting from cycle 55 as if there is no sprite DMA, and put the FLI at cycle 14 as usual. If there are sprites, the FLI will move to cycle 21. So make sure to have 10 black characters at the left edge of the screen, to cover that and the FLI bug. |
| |
JackAsser
Registered: Jun 2002 Posts: 2014 |
You just DONT reduce effect size due to lazyness. ;) |
| |
Radiant
Registered: Sep 2004 Posts: 639 |
Quote: You just DONT reduce effect size due to lazyness. ;)
Wouldn't dream of it. *whistles* |
| |
Pex Mahoney Tufvesson
Registered: Sep 2003 Posts: 52 |
A lazy solution is to only use 19 pixels high sprites. Keep the uppermost line of the sprite blank, and stretch it using D017 to the desired y-position. Then, start stretching again 19 lines further down. Then, you'll have a constant number of CPU cycles on every line.
That's what I did 2003 in BitLive4-demo
/ Pex
---
Have a noise night!
http://mahoney.c64.org |
| |
HCL
Registered: Feb 2003 Posts: 728 |
@LFT: Self-stabilising 4x4-FLI is just sooo 1994, and reminds of the death of the scene.. World of Code 3.. Don't give us that again ;).
@Mahoney: When i entered the scene in 1990 or so.. the only reason to do a d017-stretcher would be lameness, not being able to do a DYSP! Probably things were different in 2003 then ;). |
| |
Pex Mahoney Tufvesson
Registered: Sep 2003 Posts: 52 |
@HCL: Yes, d017 in 1990 was probably lame (what do I know, I was into Amiga stuff then). But at least I thought I was cool when I did d017-stretching in 1988 in Skruv ... long before you were born, kiddo. :P ;)
---
Have a noise night!
http://mahoney.c64.org |
| |
chatGPZ
Registered: Dec 2001 Posts: 11386 |
finally some decent arguments! =D |
| |
HCL
Registered: Feb 2003 Posts: 728 |
Damnit.. <word> :P |
| |
TWW
Registered: Jul 2009 Posts: 545 |
Code inside IRQ (positions the color bar split dead center):
ldy #$08
ldx #63
clc
Loop:
dec $d016
sty $d016
lda DYSP_DelayTableZP,x
sta SMC+1
SMC:
bcc *+2
and #$29
and #$29
and #$29
and #$29
and #$29
and #$29
and #$29
and #$29
and $ea
nop
lda CollTable1ZP,x
sta $d021
lda D11Table,x
sta $d011
dex
bpl Loop
Update timing table (after updating spirte positions):
CalculateDelayTableDYSP:
.pc = * "DYSP Delay Table"
// Initialize table
ldx #63
lda #$00 // 00 = no spirtes
!: sta DYSP_DelayTableZP,x
dex
bpl !-
.const kfactor = 1
// Fill in location of the sprites into the table (first and last+1 pixel)
lda $d001
sec
sbc #OffsetY
tax
lda #%00000001
sta DYSP_DelayTableZP+kfactor,x
sta DYSP_DelayTableZP+kfactor+21,x
lda $d003
sec
sbc #OffsetY
tax
lda #%00000010
ora DYSP_DelayTableZP+kfactor,x
sta DYSP_DelayTableZP+kfactor,x
lda #%00000010
ora DYSP_DelayTableZP+kfactor+21,x
sta DYSP_DelayTableZP+kfactor+21,x
lda $d005
sec
sbc #OffsetY
tax
lda #%00000100
ora DYSP_DelayTableZP+kfactor,x
sta DYSP_DelayTableZP+kfactor,x
lda #%00000100
ora DYSP_DelayTableZP+kfactor+21,x
sta DYSP_DelayTableZP+kfactor+21,x
lda $d007
sec
sbc #OffsetY
tax
lda #%00001000
ora DYSP_DelayTableZP+kfactor,x
sta DYSP_DelayTableZP+kfactor,x
lda #%00001000
ora DYSP_DelayTableZP+kfactor+21,x
sta DYSP_DelayTableZP+kfactor+21,x
lda $d009
sec
sbc #OffsetY
tax
lda #%00010000
ora DYSP_DelayTableZP+kfactor,x
sta DYSP_DelayTableZP+kfactor,x
lda #%00010000
ora DYSP_DelayTableZP+kfactor+21,x
sta DYSP_DelayTableZP+kfactor+21,x
lda $d00b
sec
sbc #OffsetY
tax
lda #%00100000
ora DYSP_DelayTableZP+kfactor,x
sta DYSP_DelayTableZP+kfactor,x
lda #%00100000
ora DYSP_DelayTableZP+kfactor+21,x
sta DYSP_DelayTableZP+kfactor+21,x
lda $d00d
sec
sbc #OffsetY
tax
lda #%01000000
ora DYSP_DelayTableZP+kfactor,x
sta DYSP_DelayTableZP+kfactor,x
lda #%01000000
ora DYSP_DelayTableZP+kfactor+21,x
sta DYSP_DelayTableZP+kfactor+21,x
lda $d00f
sec
sbc #OffsetY
tax
lda #%10000000
ora DYSP_DelayTableZP+kfactor,x
sta DYSP_DelayTableZP+kfactor,x
lda #%10000000
ora DYSP_DelayTableZP+kfactor+21,x
sta DYSP_DelayTableZP+kfactor+21,x
// EOR Fill the spritemasks
lda #$00
.for (var i = 0 ; i < 64 ; i++) {
eor DYSP_DelayTableZP+i
sta DYSP_DelayTableZP+i
}
// Replace spritemasks with timing values
.for (var i = 0 ; i < 64 ; i++) {
ldx DYSP_DelayTableZP+i
lda TimingTable,x
sta DYSP_DelayTableZP+i
}
// Flipp Table
.for (var i = 0 ; i < 32 ; i++) {
lda DYSP_DelayTableZP+i+0
ldy DYSP_DelayTableZP+63-i
sty DYSP_DelayTableZP+i+0
sta DYSP_DelayTableZP+63-i
}
rts
A bit messy but does the trick (No need to flip the table but I was trying to squeeze cycles inside the IRQ Loop). Then all You need is the actual timing values which you simply brute force with a testprogram (spritemask + adjustable value tool of some sort). |
| |
oziphantom
Registered: Oct 2014 Posts: 490 |
A lot of the docs are vague on Side borders. But from the timings it seems you can't do it on a bad line, which means you can't have chars and open side borders on the same row... also seems that vertical scrolling is out as the VIC displays black when you shift the screen up and down right? |
| |
tlr
Registered: Sep 2003 Posts: 1790 |
Quote: A lot of the docs are vague on Side borders. But from the timings it seems you can't do it on a bad line, which means you can't have chars and open side borders on the same row... also seems that vertical scrolling is out as the VIC displays black when you shift the screen up and down right?
Not true. You can have 4 sprites with open sideborder on a badline.
What do you mean by vertical scrolling, $d011 Y-scroll? Sure you can, it just moves the badlines. |
| |
ChristopherJam
Registered: Aug 2004 Posts: 1409 |
Nice work, TWW!
May I suggest replacing
lda $d001
sec
sbc #OffsetY
tax
lda #%00000001
sta DYSP_DelayTableZP+kfactor,x
sta DYSP_DelayTableZP+kfactor+21,x
with
ldx $d001
lda #%00000001
sta DYSP_DelayTableZP+kfactor-OffsetY,x
sta DYSP_DelayTableZP+kfactor+21-OffsetY,x
(might have to add $10000 to those addresses if OffsetY is greater than DYSP_DelayTableZP+1, which will add a couple of cycles to the stores, but still faster than subtracting OffsetY from X)
Should be able to save a few cycles off the second sprite's boundary plots too, by doing a CPX $d001 and selecting either %10 or %11 for the mask, then storing directly instead of lda/eor/sta
(edit - scratch that, won't work if the first two sprites are exactly 21 lines apart) |
| |
tlr
Registered: Sep 2003 Posts: 1790 |
Quoting ChristopherJam(might have to add $10000 to those addresses if OffsetY is greater than DYSP_DelayTableZP+1, which will add a couple of cycles to the stores, but still faster than subtracting OffsetY from X)
It won't add cycles. zp,x doesn't have page boundary handling. It just wraps around in zp.
What you do is just take &$ff on the address to utilize that. |
| |
ChristopherJam
Registered: Aug 2004 Posts: 1409 |
Quoting tlr
It won't add cycles. zp,x doesn't have page boundary handling. It just wraps around in zp.
What you do is just take &$ff on the address to utilize that.
Excellent point! |
| |
oziphantom
Registered: Oct 2014 Posts: 490 |
Opening sideborders in the bottom border seems to work fine, but as I try and move the code to the top border, i.e Y=20, the sprites shown down the bottom of the screen where the border does open and at the top of the screen but the border is closed... Does it not work in the top border? |
| |
chatGPZ
Registered: Dec 2001 Posts: 11386 |
sure does |
| |
algorithm
Registered: May 2002 Posts: 705 |
Quote: Opening sideborders in the bottom border seems to work fine, but as I try and move the code to the top border, i.e Y=20, the sprites shown down the bottom of the screen where the border does open and at the top of the screen but the border is closed... Does it not work in the top border?
Are you updating sprite Y position before running the code at raster 20? (Even though the sprites appear at the bottom (and the bottom half at the top) |
| |
oziphantom
Registered: Oct 2014 Posts: 490 |
Nope just me being an idiot, I was pretty sure that the D012/11 was going to be $100+ by the point I was checking to make sure D011 was positive, nope. So my is D011 positive check was happening just before $100 so it passed then my D012 loop to wait to 20 happened so the raster happened at 114. |