← Back to context

Comment by sokoloff

5 years ago

I led a small dev team to port a PC title to the original PSX. It had a physics engine that ran at 30Hz and needed to run close to isochronously to preserve gameplay.

One of the hacks we created was a limited pre-emptive multi-tasking system on the original Playstation to run our physics engine at a constant frequency. We'd asked Sony US who escalated to Sony Japan and told us it couldn't be done and we should write our code in a cooperative multi-tasking style.

We eventually figured out how to use the vertical blank interrupt to save all the registers, modify the return address to return from the interrupt to our physics code, then our physics code would run (not in the interrupt context), then restore the registers and longjmp back to the main game code which had been originally interrupted by the vertical blank interrupt.

This wasn't general-purpose pre-emption and had only fixed divisors of the screen refresh rate available, but was enough to get our physics engine to run close to isochronously.

Side note 1: our game (NASCAR Racing) was comparatively terrible and I remember being in awe of what the Crash Bandicoot, Ridge Racer, and other early PSX teams accomplished in gameplay and graphics.

Side note 2: as unconventional as the PSX was, it was positively mainstream compared to its contemporary Sega Saturn.

You probably know this, but for the benefit of others: this technique of using the vertical blank interrupt to run code was very common in the 8-bit (Atari/Apple/C64) era. In fact you really couldn't write a decent game without hooking the vertical blank interrupt (and often the per-scanline horizontal interrupt as well).

It's interesting that you were able to adapt this technique to PS1 hardware; does that game run properly under emulators or does it give them fits?

  • Indeed: that's where I learned it, having written both H/VBLANK ISRs on the beloved 8-bit Atari I grew up with. HBlank ISRs were needed to get more than 4 + 1 player-missile (sprites) to work.

    I don't know if the game runs on emulators, but I know it runs correctly on a mid-generation PS3 and I'd expect that VBlank interrupt handling would be implemented by an emulator, so I'd expect it should work.

    PS: Massive, massive respect for you and the rest of the Crash team. When that game came out, we couldn't comprehend how you were able to pull off that game on the same hardware we were using. Much respect (and thanks for the great gaming memories)!

I started to read your side note 2 and immediately wondered what you would have called the Saturn but then you addressed it.

> then restore the registers and longjmp back to the main game code which had been originally interrupted by the vertical blank interrupt.

So that's really like, half preemptive with the return side really being cooperative style?

  • Yes, exactly (and why I described it as limited).

    The physics thread (with interleaved joystick read code on PC) did have to yield back to the main/graphics thread. That was no issue in our case; I've not thought deeply about it, but it seems like the method is likely extensible to do context switches among multiple [unaware/non-cooperating] threads.