I'm looking for some information on the best way to organise timings within a frame...
I've got a game working where all of the sprite routines are fast enough to work within the top and bottom border of the frame, so am therefore keen not to have to double buffer. The only thing is, after the sprites are printed in the top border I'm then executing my logic routines etc and the time these routine takes is variable, sometimes only a few scan lines, other times it's a big chunk of time.
Whilst developing, I used a HALT instruction to wait until the bottom(ish) border and this worked to a degree. But if the logic routines take a long time one frame, sometimes this HALT instruction is pushed down and the frame over runs (even though the routines are all quick enough to fit in one frame at the timings the HALT should send you to).
So, what do I do? I'm guessing interrupts (as I want to split the screen colours too) but couldn't find any reliable information on how the best way is to do it on a classic CPC...?
I think you may count ints, the int #0 is on VBL so if your interrupt code checks vbl , you can choose which interrupt to wait instead of using halt.Someting like this :
push AF
push BC
push HL
;get if we are on VBL
ld B,#F5
in A,(C)
rra
jr nc,int_not_VBL
ld A,#FF
ld (int_counter),A
int_not_VBL
int_counter EQU $+1
ld A,0
inc A
and 7
ld (int_counter),A
add A,A
int_table EQU $+1
ld HL,int_default_table
ld C,A
ld B,0
add HL,BC
ld A,(HL)
inc HL
ld H,(HL)
ld L,A
jp (HL)
.int_done
pop HL
pop BC
pop AF
ei
ret
;interrupt table - 8 vectors
;called code must finished with jp/jr int_done (AF,BC and HL are already saved so no need to push them)
.int_default_table
dw int_done ;branch int #0
dw int_done ;branch int #1
dw int_done ;branch int #2
dw int_done ;branch int #3
dw int_done ;branch int #4
dw int_done ;branch int #5
dw int_done ;useless but keep as it
dw int_done ;useless but keep as it
You have the previous int # at int_counter so if you need to start your sprite code after int #2 , just do something like
wait_int
ld A,(int_counter)
cp 3
jr c,wait_int
Btw , if you need to refresh sprites each 50fps without taking in count other calculations, it is a bit tricky but not very different, just need to save PC,SP and other registers and restore stack after doing your stuff (a good thing to do is using another stack for your sprite code), let's call that sort of multitasking...
In some cases I've found it useful to do something like fano's suggestion, but rather than waiting for a particular interrupt to begin the sprite code, I begin the game loop at the first interrupt below the game's play area and so dont key the game loop to the VBL directly. That way there is a larger continuous block of cpu time to get all the sprites out of the way, and after that the other processes can take whatever time they need.
Excellent fano, this worked for me!!! Great idea using the interrupt as a timer.
Quote from: fano on 10:10, 24 April 13
Btw , if you need to refresh sprites each 50fps without taking in count other calculations, it is a bit tricky but not very different, just need to save PC,SP and other registers and restore stack after doing your stuff (a good thing to do is using another stack for your sprite code), let's call that sort of multitasking...
Yes, I thought I would need to preserve the SP, however I tried the code and when the interrupt is called, I did this:
push af ; preserve all registers
push bc
push de
push hl
push ix
push iy
exx
ex af, af'
push af
push bc
push de
push hl
..and when it's finished, I did this:
.int_done
pop hl ; restore all registers
pop de
pop bc
pop af
ex af, af'
exx
pop iy
pop ix
pop hl
pop de
pop bc
pop af
ei
ret
...and this seems to be working fine (in WinApe). I do use the stack quite a bit in my routines, so did I miss anything?
Quote from: fano on 10:10, 24 April 13
just need to save PC,SP and other registers and restore stack after doing your stuff (a good thing to do is using another stack for your sprite code)
Okay, looks like I got lucky with my previous routines as I've just written another one and it throws the timings off.
So whats the best way to preserve PC/SP and restore the stack with your routine?
PC is saved on top of the stack so you can let it on (as long you do not need to know exactly where code was interrupted) , as you already do.For SP , you can save it with ld (addr),SP and restore it with ld SP,stack_addr with something like this :
(some push stuff)
LD (save_stack),SP
ld SP,local_stack
and to finish with something like
save_stack EQU $+1
ld SP,0
(some pop stuff here)
ei
ret
Btw, this is not an obligation to have a local stack , you can use your main stack but beware to preserve enough space to avoid stack overides your data/code.Note too, if you use an int counter and your code takes more than 1 int (something like 52lines or a bit more then 3000 µs) , your int counter will not be the same as you miss the next interrupt.This method is very usefull when you want to do some raster/split raster (or if you need have code that refresh something on screen every frame) under interrupts because you can use stack as jump table for each line.
Thanks again fano, this worked perfectly.
One thing I do find interesting is that I'm using the interrupt purely as a timer, so my interrupt routine just waits for VBL and then sets the timer during the frame like you demonstrated.
Then, in my main loop, I also wait for the VBL and then execute certain routines at the set times. But even though I'm waiting for the VBL twice in effect (once during interrupt and once during main loop), my code is still running at 50hz, like I want it to.
I see from Grimware's website that the Vsync takes place during quite a few scan lines (chars 30 to 33). I'm therefore assuming that the interrupt code runs so quickly that I'm still within the VBL so I can check it again during my main loop...?
As I said, it's all working but I want to understand exactly why it does!
Quote from: redbox on 20:58, 25 April 13I see from Grimware's website that the Vsync takes place during quite a few scan lines (chars 30 to 33). I'm therefore assuming that the interrupt code runs so quickly that I'm still within the VBL so I can check it again during my main loop...?
This is correct but be carefull.Afaik VBL duration is different for different CRTC types so that may not work on other CPC than the one your are testing on, better testing the int if you use a counter and doing your stuff after the first int.
Note too vsync comes after theses lines according to r7 settings (that causes upper border)
Okay, that explains it nicely, thanks.
Will use the timer instead then, don't want the game to be CRTC dependent!!
Quote from: fano on 03:13, 26 April 13
This is correct but be carefull.Afaik VBL duration is different for different CRTC types so that may not work on other CPC than the one your are testing on, better testing the int if you use a counter and doing your stuff after the first int.
Tried this:
main_loop: ld a,(int_counter) ; wait for v-sync
cp 0
jr c,main_loop
and it didn't work for me...
Do I have to wait until the first INT (i.e. CP 1) to use this method? If so, I loose all of the frame time in the top border :o
Quote from: redbox on 10:21, 29 April 13
Tried this:
main_loop: ld a,(int_counter) ; wait for v-sync
cp 0
jr c,main_loop
and it didn't work for me...
Do I have to wait until the first INT (i.e. CP 1) to use this method? If so, I loose all of the frame time in the top border :o
Perfectly logical ! CP acts like SUB , if you do X - 0 , you will never get a carry ! something like would work better :
main_loop: ld a,(int_counter) ; wait for v-sync
or A
jr nz,main_loop
Quote from: fano on 10:34, 29 April 13
CP acts like SUB , if you do X - 0 , you will never get a carry !
D'oh!
Thanks :D
You probably should never be using a carry test for any interrupt. For example, if you want to do something on interrupt 4, and it only takes 2000 microseconds to get back to the loop test it'll also do the same thing again on interrupt 5. (So use NZ unless you also use frame flyback or something first).
.wait
ld a,(intno)
cp 4
jr nz,wait
If you don't need to call other interrupt routines, you can keep your interrupt routine very short:
.interrupt
push af
ld a,#f5
in a,(0)
rra
sbc a
jr nz,resint
.intno equ $ + 1
ld a,0
.resint
inc a
ld (intno),a
pop af
ei
ret
You can even use EX AF,AF' instead of PUSH and POP if you're not using the register anywhere else. You shouldn't need to store the SP or any other registers as this routine doesn't use them, unless you are using PUSH to write data to screen or similar and it could use 4 bytes while interrupting (PUSH/POP version, EX AF,AF' version only uses 2 bytes).
Sorry, posted here instead of EgoTrip's Game Progress topic as I followed the link. Perhaps this can be moved over there.