Author Topic: Using stack and interrupts  (Read 2024 times)

0 Members and 1 Guest are viewing this topic.

Offline ssr86

  • CPC664
  • ***
  • Posts: 120
  • Country: pl
  • Liked: 48
  • Likes Given: 21
Using stack and interrupts
« on: 10:28, 16 October 13 »
I need some advice... I'm using stack and interrupts. Currently I'm using stack for fast drawing of unmasked sprites, restoring and saving background. Because of the problem with changing sp and executing interrupts I'm doing that in the interrupts. Another way would be to make the code execute always in the same cpu-time so I know when the interrupts occur and restore the sp for them. Also I could not use the interrupts at all then (when the code is "synchronized")...
Beside fast drawing with stack, I'm using interrupts for music, screen change handling, palette changes (because of this it would be hard without interrupts for me).

Maybe there's a smarter way (because now I'm bounded by the 3328 nops for an interrupt...although that's not a huge problem...just have to divide the procedures)?

Offline Axelay

  • 6128 Plus
  • ******
  • Posts: 587
  • Country: au
  • Liked: 385
  • Likes Given: 88
Re: Using stack and interrupts
« Reply #1 on: 11:17, 16 October 13 »
Are your sprites all the same size?  Or could they be handled in groups of the same size?  If that was the case, and you also have some interrupts that are not used (except by the current sprite code), maybe you could disable interrupts for a partial frame, process your set number of sprites that should use a known amount of cpu time, then re-enable interrupts which should be at the same point each frame so the interrupts you do need remain stable?

Offline ssr86

  • CPC664
  • ***
  • Posts: 120
  • Country: pl
  • Liked: 48
  • Likes Given: 21
Re: Using stack and interrupts
« Reply #2 on: 15:44, 17 October 13 »
My sprites are:
- the player - 32x40 masked pixels (mode 0 and double height); for now it's of standard type but will be compressed (if I write a compressor tool...); however during jump the dimensions will change...
- 3 "firebazes" 24x96 not-masked pixels (mode 0 and double height) divided into 6 tiles of 24x16 pixels (for now because maybe 24x24 would be better); each tile is of compiled type.

My screen is 160x128 double height mode 0 pixels. I'm going with 16,5 fps.
My upper 32 lines are for scores and time; the latter 96 lines are the game area. In the game area I'm aiming for some pallette changes like after defeating the last boss in "Prehistorik 2" and so I should need the 4 interrupts each frame for the pallette changes

Drawing one 32x16 tile of the flame takes from 1035 nops to 1305 nops.
Restoring the bg for the player sprite takes 6100 nops (currently divided between two interrupts so really 2*3050 )
Saving bg for the player sprite takes 4460 nops (also divided between two interrupts so 2*2230)
Drawing the player sprite now takes up 13545 nops (but should take much less when I make it of compressed type...)

It seems I could disable the interrupts for restoring and saving bg. Put the di just before the interrupt should start because the code (so I think) should be more or less of known time...

Should I avoid putting too much into interrupts? Why?

ps. sorry for my late answer (and thanks for your fast answer).

Offline Axelay

  • 6128 Plus
  • ******
  • Posts: 587
  • Country: au
  • Liked: 385
  • Likes Given: 88
Re: Using stack and interrupts
« Reply #3 on: 18:23, 17 October 13 »
That does sound like a lot to do under interrupt.  Do you only have one screen?  I was wondering in what way are you using the stack to speed the processes you mentioned?

Offline ssr86

  • CPC664
  • ***
  • Posts: 120
  • Country: pl
  • Liked: 48
  • Likes Given: 21
Re: Using stack and interrupts
« Reply #4 on: 18:38, 17 October 13 »
I have two screens (like in http://www.cpcwiki.eu/forum/programming/32kb-screen-ram/) and switch between them every three video frames.
In interrupts I "only" draw the tiles of "flames" and restore/save background of the masked player sprite.

In what way I use stack to speed up the code?
Well basically I use the standard "pop values from buffer and push them to screen" (like in "standard" type sprites) for restoring the bg analogically for saving bg but in the opposite direction... For the tiles I load the 16bit register with some values and push them to screen ("compiled" type sprites). From what I remember measuring earlier I think that the drawing of tiles for the flames is like 2 times faster and the saving\restoring takes somewhat like 2/3 of what it took previously (methods using ldi...I think they where from http://www.cpcwiki.eu/forum/programming/mode-3/msg22570/#msg22570 which is one of your examples)

I'll post some code later when I'll be able to.

Offline ssr86

  • CPC664
  • ***
  • Posts: 120
  • Country: pl
  • Liked: 48
  • Likes Given: 21
Re: Using stack and interrupts
« Reply #5 on: 20:26, 17 October 13 »
Here's the code:

the code for drawing one double line of the 32x16 tiles looks something like this:
Code: [Select]
; in hl is the address of a table with screen addreses of each line of the tile where I should start pushing to
ld sp,hl
pop de
inc l
inc l
; now in de is the screen address where to push to
ex de,hl
ld sp,hl
exx:push bc:push af:push af:push af:push af:push bc:exx
set 3,h
ld sp,hl
exx:push bc:push af:push af:push af:push af:push bc:exx
ex de,hl

The code for restoring one double line of the bg behind the player sprite:

Code: [Select]
; bc is &0010 which is equal to width of the sprite in bytes
; get the line address from the table of line addreses held in hl
; there is a procedure to calculate the addreses of each line of the sprite
push hl
ld a,(hl)
inc l
ex af,af'
ld a,(hl)
ld h,a
ex af,af'
ld l,a

add hl,bc

ex de,hl
ld (@isrbg_SaveSP1+1),sp
ld sp,hl
; pop data from buffer (8 words = 32 pixels = sprite width)
pop ix:pop iy:pop bc:pop af:ex af,af':exx:pop af:pop bc:pop de:pop hl:exx
ex de,hl
ld sp,hl
; push data to screen (odd lines)
exx:push hl:push de:push bc:push af:ex af,af':push af:exx:push bc:push iy:push ix
set 3,h
ld sp,hl
; push data to screen (even lines)
exx:push hl:push de:push bc:push af:ex af,af':push af:exx:push bc:push iy:push ix
;; correct the scrren and buffer offsets (de and hl - add sprite width in bytes)
ld bc,&0010
ex de,hl
add hl,bc
ex de,hl
@isrbg_SaveSP1
ld sp,0
pop hl
inc l
inc l

The code for saving a double line of the bg:

Code: [Select]
;bc=&0010 = sprite width in bytes
;get the line address from table held in hl
push hl
ld a,(hl)
inc l
ex af,af'
ld a,(hl)
ld h,a
ex af,af'
ld l,a

ex de,hl
add hl,bc
ex de,hl

ld (@ssbg20l_SaveSP1+1),sp
ld sp,hl
; pop data from buffer (8 words = 32 pixels = sprite width)
pop ix:pop iy:pop bc:pop af:ex af,af':exx:pop af:pop bc:pop de:pop hl:exx
ex de,hl
ld sp,hl
; push data to screen (odd lines)
exx:push hl:push de:push bc:push af:ex af,af':push af:exx:push bc:push iy:push ix
; correct the scrren and buffer offsets (de and hl - add sprite width in bytes)
ld bc,&0010
ex de,hl
add hl,bc
@ssbg20l_SaveSP1
ld sp,0
pop hl
inc l
inc l

Offline ralferoo

  • Supporter
  • 6128 Plus
  • *
  • Posts: 970
  • Country: gb
  • Liked: 583
  • Likes Given: 222
Re: Using stack and interrupts
« Reply #6 on: 23:10, 17 October 13 »
Should I avoid putting too much into interrupts? Why?
Yes. No. It depends.

There's no real reason not to use interrupts as a tool to do whatever you want. However, I would offer this advice if you're also planning to use the interrupts for timing:

Firstly, read this page for how interrupts are triggered: Interrupt Generation Facility of the Amstrad Gate Array

Then be aware that if you're going to disable interrupts for more than 52 raster lines (so 3328us), you'll probably have to handle this carefully. You'll notice that bit 5 of the counter is the key. It's cleared when the Z80 acknowledges an interrupt and interrupts are only triggered when the count reaches 52 or 2 lines after VYSNC when bit 5=1.

Consequently, if you have interrupts disabled when the interrupt should be triggered, if you re-enable them within 32 scanlines, the interrupt will be serviced and everything will continue as normal for the next interrupt.

If you don't respond within 52 scanlines but within 84 scanlines, the next interrupt will be scheduled as normal, you'll have just missed one interrupt.

In the grey area between 32 and 52 scanlines, when the interrupt is acknowledged, it'll clear bit 5 and so the next interrupt will be delayed by 32 lines. If you allow every other interrupt, you'll see them all appearing 32 lines late, and then 2 lines after VSYNC, the interrupt that should have been triggered will clear the counter, but not actually trigger an interrupt.

If safest approach is to never disable interrupts for more than 32 scanlines (2048us), and just insert an EI and DI pair somewhere you visit less frequently than that. If your tile drawing code takes 1305 lines, that's ideal - stick a DI at the beginning and EI at the end. Also, this code is likely to be the place where you'll want to use SP for fast accesses anyway, so just do that inside the DI ... EI block.

One thing I do in my code is to store a JP xxxx at #38 and change the vector stored at #39 every interrupt. It's even totally permissible to then use specific areas of the screen for certain tasks knowing you don't even need to save registers in the interrupt because the code that'll run after the interrupt exits doesn't need them anymore!

It's also possible to multitask if you have several stacks - just push all registers on entering the the interrupt, save sp and change to a different one, pop all registers and return.

Offline ssr86

  • CPC664
  • ***
  • Posts: 120
  • Country: pl
  • Liked: 48
  • Likes Given: 21
Re: Using stack and interrupts
« Reply #7 on: 23:59, 17 October 13 »
Quote
Firstly, read this page for how interrupts are triggered: Interrupt Generation Facility of the Amstrad Gate Array

Then be aware that if you're going to disable interrupts for more than 52 raster lines (so 3328us), you'll probably have to handle this carefully. You'll notice that bit 5 of the counter is the key. It's cleared when the Z80 acknowledges an interrupt and interrupts are only triggered when the count reaches 52 or 2 lines after VYSNC when bit 5=1.

Consequently, if you have interrupts disabled when the interrupt should be triggered, if you re-enable them within 32 scanlines, the interrupt will be serviced and everything will continue as normal for the next interrupt.

If you don't respond within 52 scanlines but within 84 scanlines, the next interrupt will be scheduled as normal, you'll have just missed one interrupt.

In the grey area between 32 and 52 scanlines, when the interrupt is acknowledged, it'll clear bit 5 and so the next interrupt will be delayed by 32 lines. If you allow every other interrupt, you'll see them all appearing 32 lines late, and then 2 lines after VSYNC, the interrupt that should have been triggered will clear the counter, but not actually trigger an interrupt.


Thanks for the info. I wanted to ask about what happens if I miss an interrupt and etc.:)

Quote
If safest approach is to never disable interrupts for more than 32 scanlines (2048us), and just insert an EI and DI pair somewhere you visit less frequently than that. If your tile drawing code takes 1305 lines, that's ideal - stick a DI at the beginning and EI at the end. Also, this code is likely to be the place where you'll want to use SP for fast accesses anyway, so just do that inside the DI ... EI block.

Will try that...

Quote
One thing I do in my code is to store a JP xxxx at #38 and change the vector stored at #39 every interrupt. It's even totally permissible to then use specific areas of the screen for certain tasks knowing you don't even need to save registers in the interrupt because the code that'll run after the interrupt exits doesn't need them anymore!

Already doing something similar (I think) based on the interrupts code found in "Edge grinder" source code... Although I use IM2.
I have 18 interrupt routines (with my "game" I'm going for 16,5fps).


Offline trabitboy

  • Supporter
  • CPC664
  • *
  • Posts: 90
  • Country: be
  • Liked: 50
  • Likes Given: 74
Re: Using stack and interrupts
« Reply #8 on: 13:35, 22 October 13 »
please forgive a noob question from a z80 noob:  ;D
I read here that the stack shouldn't be used in a program that uses interrupts.
It's also mentioned in the example I used to try and sync my program :
Using interrupts - NoRecess
although norecess's explanation is really good ( and his sample program actually works :)  )
he doesn't really explain why it is a bad thing .


From what I see ( as a noob ),
if on start of the interrupt you preserve all registers, restore them,
and clean the stack to its initial state it seems ok?
Am I missing something?
Is it a performance related thing?


( pls forward me to a beginner's faq if this is too noob  :blank: ... )

Offline ssr86

  • CPC664
  • ***
  • Posts: 120
  • Country: pl
  • Liked: 48
  • Likes Given: 21
Re: Using stack and interrupts
« Reply #9 on: 13:59, 22 October 13 »
Here's written why it is troublesome to use stack when interrupts are enabled (the 1st post by Grim):

Push'n'Pop | Amstrad CPC Demoscene | Stack and interuptions
Push'n'Pop | Amstrad CPC Demoscene | Stack and interuptions

Offline ralferoo

  • Supporter
  • 6128 Plus
  • *
  • Posts: 970
  • Country: gb
  • Liked: 583
  • Likes Given: 222
Re: Using stack and interrupts
« Reply #10 on: 22:58, 22 October 13 »
The problem is simply that if SP is pointing at some of your data, if an interrupt occurs, it'll trash data downwards from SP. At the minimum, it'll overwrite 2 bytes (the return address) but most interrupt routines also save registers etc.

If you're going to use SP in your code, it's totally fine, just DI first and EI as soon as SP is pointing at an area of memory you don't mind being destroyed.

There's an example in my Sugarlumps demo, https://github.com/ralferoo/sugarlumps/blob/master/draw_poly.asm from line 165 onwards. There's a bit of documentation on how these stack buffers are used in https://github.com/ralferoo/sugarlumps/blob/master/buffers.asm. Note there's no DI/EI in this example as even though I'm swapping SP between multiple stacks fairly regularly (3 render queues and the main stack), I am actually using it as a genuine stack where memory below SP is allowed to be trashed. If you were using SP for PUSH or POPping graphics data, you'd definitely want interrupts disabled to stop graphical corruption.