IM2 corrupting stack-based redraw

The place for codemasters or beginners to talk about programming any language for the Spectrum.
Post Reply
zxade
Berk
Posts: 10
Joined: Tue Feb 19, 2019 12:14 pm

IM2 corrupting stack-based redraw

Post by zxade » Sun Aug 18, 2019 10:48 pm

I'm developing a game which uses a scrolling screen area and a back buffer. I want to use an area of 24x24 chars and have been experimenting to see how quickly I can transfer the buffer to the screen. I have a fast method which uses the stack, popping data from the buffer and pushing it to the screen though it takes just over 1 frame to complete due to the size of the area I am using. I also want to have AY music playing in the background via an IM2 routine. However I've noticed that the screen display gets corrupted when the interrupt routine triggers during a redraw. Obviously at that point the interrupt handler is pushing PC to the stack which at that time happens to be pointing to the screen or the buffer. I tried disabling interrupts before the redraw and enabling them afterward but this causes the music to sound all wrong since it no longer plays at 50 fps.

Many commercial games use the pop/push screen update method and also played interrupt driven AY music. Did they only use an area they could redraw in under a frame? or is they a way I can handle the situation that is occurring?
0 x

User avatar
Seven.FFF
Manic Miner
Posts: 338
Joined: Sat Nov 25, 2017 10:50 pm
Location: USA

Re: IM2 corrupting stack-based redraw

Post by Seven.FFF » Sun Aug 18, 2019 11:05 pm

Yes, generally you would turn off interrupts while you use a routine that abuses the stack pointer, if the IM2 handler uses the stack at all.

If your IM2 handler is sensitive to dropped frames like a music player, then it’s a good idea to keep the routine that abuses the SP short, or else raise an interrupt periodically with ei:halt:di, so that it never overruns the frame.

If you choose to keep interrupts enabled, you can sometimes work around the stack corruption by having the IM2 handler back up SP, set it to a safe area, and restore it before exiting.

Generally, if you have a good idea of what your timings are, and how many Ts each routine takes, you can be fairly confident in designing and scheduling something that never overruns or does unexpected things. One technique I use a lot is to schedule different tasks to happen every other frame, or every fourth frame, etc. By allocating time slices this way, you can fill up the available time such that there is a fairly even workload each frame, and be confident nothing will overrun.
1 x
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
seven-fff.com/blog

User avatar
Seven.FFF
Manic Miner
Posts: 338
Joined: Sat Nov 25, 2017 10:50 pm
Location: USA

Re: IM2 corrupting stack-based redraw

Post by Seven.FFF » Sun Aug 18, 2019 11:14 pm

It really also depends on whether your are running frame-synced code or not - that is, whether you have any halt instructions in your main loop.

If you do, your routines will tend to happen at the same, or at least predictable, places every frame, and you have much more control over whether any given routine is going to collide with the VBI interrupt and your IM2 handler.

If you don’t, and your just letting everything run as fast as it can, then it’s hsrder to predict, and doing the IM2 stack backup and restore might be a better option.

You can also distinguish between one-off and recurring routines. A one-off routine at the start of a level which does a fast CLS using SP might best be handled simply by disabling interrupts for the duration - and by designing the game so there is never any audio playing at that instant. A routine that runs every single frame and abuses SP might better be dealt with by scheduling it immediately after a halt, or at another well-defined moment during the frame, perhaps using the floating bus for timing.
1 x
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
seven-fff.com/blog

User avatar
djnzx48
Manic Miner
Posts: 537
Joined: Wed Dec 06, 2017 2:13 am
Location: New Zealand

Re: IM2 corrupting stack-based redraw

Post by djnzx48 » Sun Aug 18, 2019 11:37 pm

There was a similar topic previously posted here: viewtopic.php?f=6&t=166

There are some tricks that allow you to use the stack pointer for other purposes while still allowing interrupts to be enabled. If your code only uses a certain register for pops, so that the register always contains the value at the head of the stack, then the interrupt routine can easily restore the value. But this wouldn't help for screen copies using more than one register.

There was a method described in that thread of using checksums to restore the corrupted data, which could potentially be a solution for copying the screen.

Another option is to play your music at 25Hz so interrupts only need to be enabled every other frame. Gluf is one example of a game that uses 25Hz music.
1 x

zxade
Berk
Posts: 10
Joined: Tue Feb 19, 2019 12:14 pm

Re: IM2 corrupting stack-based redraw

Post by zxade » Mon Aug 19, 2019 8:26 am

Seven.FFF wrote:
Sun Aug 18, 2019 11:05 pm
If you choose to keep interrupts enabled, you can sometimes work around the stack corruption by having the IM2 handler back up SP, set it to a safe area, and restore it before exiting.
The interrupt routine does save the SP and point it elsewhere before saving registers prior to playing the music. However during the activation of the interrupt itself the CPU has already pushed the program counter onto the stack. If this occurs whilst I am using it to pop values from the buffer it causes two bytes to be changed in the buffer which are not restored when the interrupt completes (I'm using multiple registers to pop/push so also can't identify what the value was previously).

I normally start my game loop with a halt instruction (or more depending on current frame rate) but this is the first time I have been "abusing" the stack and thus realised this problem. Thanks for the suggestions @Seven.FFF regarding keeping track of timings and where things are at during a frame, this is not something I've considered much before but should be possible using the emulator as a tool for counting t-states.
djnzx48 wrote:
Sun Aug 18, 2019 11:37 pm
Another option is to play your music at 25Hz so interrupts only need to be enabled every other frame. Gluf is one example of a game that uses 25Hz music.
It never crossed my mind at all to tackle it this way...I have increased the speed at which the tracker plays the music and it will now play it correctly at 25Hz, so I can now disable/restore interrupts quite happily with no ill affect! A very simple solution, thanks @djnzx48.
0 x

Ralf
Dynamite Dan
Posts: 1277
Joined: Mon Nov 13, 2017 11:59 am
Location: Poland

Re: IM2 corrupting stack-based redraw

Post by Ralf » Mon Aug 19, 2019 10:04 am

It never crossed my mind at all to tackle it this way...I have increased the speed at which the tracker plays the music and it will now play it correctly at 25Hz, so I can now disable/restore interrupts quite happily with no ill affect! A very simple solution...
I'm not sure what tools you are using but if you use some standard tracker like Vortex Tracker then playing music every second frame isn't a good idea.

Yes, you can speed up the tune in the tracker so when you play it every second frame it plays at normal speed. But when you play it every second frame and not every frame as supposed, you destroy "instruments" used in the tune. The melody will be recognizable but the sound becomes somehow distorted.

Of course, it's your choice if you accept this loss of quality or not.
0 x

User avatar
djnzx48
Manic Miner
Posts: 537
Joined: Wed Dec 06, 2017 2:13 am
Location: New Zealand

Re: IM2 corrupting stack-based redraw

Post by djnzx48 » Tue Aug 20, 2019 12:08 am

Another possibility if you don't want to alter your music to play at 25Hz: when your interrupt is called, you can get the previous PC value from the stack and use it to find out where you were in the loop before getting interrupted. From this, you can infer which register holds the relevant memory value and restore it. Using this method would require some care to ensure the first or last addresses from each PUSH/POP group don't get corrupted.

Edit: As your buffer area is smaller than the full screen size, the corruption issues on the first or last addresses could be avoided with padding bytes. Alternatively you could store backup values for those addresses somewhere in memory.

The goal of avoiding stack memory corruption is to ensure: for each memory location immediately below the stack pointer, whenever an interrupt has a possibility to occur, either
  • you don't care about the value (it may be overwritten with new data)
  • you have a backup of the value stored somewhere (either in memory or in a register).
Using this principle, it then becomes the job of the interrupt routine to locate the backup value and place it in the proper address.
0 x

zxade
Berk
Posts: 10
Joined: Tue Feb 19, 2019 12:14 pm

Re: IM2 corrupting stack-based redraw

Post by zxade » Tue Aug 20, 2019 8:53 pm

Hmmm, that would be clever, I'll possibly experiment to see what it would take to try to implement something like this. I guess it explains why so many commercial games opted for using the simpler multiple LDI copy method.
0 x

AndyC
Manic Miner
Posts: 251
Joined: Mon Nov 13, 2017 5:12 am

Re: IM2 corrupting stack-based redraw

Post by AndyC » Wed Aug 21, 2019 5:07 am

A simpler solution is to disable interrupts only while fetching corruptible data. If an interrupt occurs while PUSHing data to the screen, it might lead to visible effects but there is a good chance the ISR will complete in time and the display data will be naturally corrected. It is really only while fetching buffer data that problematic stack corruption can occur.
0 x

User avatar
djnzx48
Manic Miner
Posts: 537
Joined: Wed Dec 06, 2017 2:13 am
Location: New Zealand

Re: IM2 corrupting stack-based redraw

Post by djnzx48 » Wed Aug 21, 2019 5:29 am

The only issue is when you're on the leftmost column of the screen, and pushing the final byte pair of the row. Depending on the screen size, you could end up corrupting the border, or the contents of other display rows. If the visible buffer contents don't take up the whole screen, then you'll only have border corruption to deal with.

Edit: and if you get corruption on the screen, you're copying it from a buffer anyway, so the interrupt routine can just recopy the affected bytes.
0 x

Post Reply