News:

Printed Amstrad Addict magazine announced, check it out here!

Main Menu
avatar_redbox

Frame Timings

Started by redbox, 09:49, 24 April 13

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

redbox

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...?

fano

#1
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...
"NOP" is the perfect program : short , fast and (known) bug free

Follow Easter Egg products on Facebook !

Axelay

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.

redbox

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?

redbox

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?

fano

#5

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.
"NOP" is the perfect program : short , fast and (known) bug free

Follow Easter Egg products on Facebook !

redbox

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!

fano

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.
"NOP" is the perfect program : short , fast and (known) bug free

Follow Easter Egg products on Facebook !

fano

Note too vsync comes after theses lines according to r7 settings (that causes upper border)
"NOP" is the perfect program : short , fast and (known) bug free

Follow Easter Egg products on Facebook !

redbox

Okay, that explains it nicely, thanks.

Will use the timer instead then, don't want the game to be CRTC dependent!!

redbox

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

fano

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
"NOP" is the perfect program : short , fast and (known) bug free

Follow Easter Egg products on Facebook !

redbox

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

Executioner

#13
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.

Powered by SMFPacks Menu Editor Mod