A couple of days ago I added some sound effects to my ZX Spectrum 48K “work in progress” game, and I found that the few cycles the beeper engine uses in the interrupt handler broke my tight timing and, specially on the top of the screen, there will be flicker when drawing a few sprites on the same line. Which is a shame.
Initially I thought that it wasn’t a big deal but, because how I decided to implement coloured sprites, the flickering is specially ugly –to my eyes, at least–. So I knew about a technique that allows you some extra time besides the one you get by syncing with the interrupt: The Definitive Programmer’s Guide to Using the Floating Bus Trick on the ZX Spectrum, by Ast A. Moore.
From that page:
In the context of the ZX Spectrum, the floating bus trick refers to exploiting a hardware quirk of these machines, where a value being read by the ULA from the data bus can also be read by the CPU.
Which in a nutshell means you can prepare some specific data on screen, usually in the colour attributes, and listen to the ULA using the floating bus trick to sync with it when it is processing that specific data.
For example: in my game I have drawn a “HUD” –head-up display; in a computer game, the part of the screen that shows information like the number of lives left or the score– with a specific colour attribute not used anywhere else in the game, and then I have a specifically timed routine that listens the floating bus to wait until the ULA is processing those attributes. Because the attributes are in the bottom of the drawable screen, I get extra time to draw my sprites –the HUD and the bottom border–.
On that screenshot of the game I’m drawing a red line on the border when I sync with the HUD attributes after listening to the floating bus. And with that extra time, the flickering is gone!
Initially it was thought that this trick didn’t work on +2A/+3 models. I had a +2A back in the day and I can confirm that a lot of games –specially for 48K models– didn’t work on my machine. Ast did some excellent research a few years ago and he came up with an implementation for those models. It only took 30 years!
Just as an example –you should read Ast’s article–, this is my current version of the code:
; HUD attr: bright paper cyan, black ink
; will OR 1 in +2A/+3 models
BUS_ATTR equ 0x68
wait_bus:
ld a, (0x0b53)
cp #0x7e
jr z, other_bus
wait_bus_loop:
ld a, #0x40
in a, (0xff)
cp #BUS_ATTR
jp nz, wait_bus_loop
xor a
ret
other_bus:
ld a, (#0x5800)
ld a, #0x0f
in a, (0xfd)
cp #(BUS_ATTR | 1)
jp nz, other_bus
xor a
ret
It looks like the floating bus is tricky to implement on emulators. The “classic” floating bus is perfectly emulated by almost all emulators, but the floating bus of the +2A/+3 models is not widely supported. I don’t have an list, but I have confirmed RVM and CLK support it fine!
I detect the +2A/+3 models and just branch to the special sync code for them. If the floating bus is not there, the code will loop forever. That could be avoided by detecting if the floating bus exists before hand and falling back to use vsync –even if that means flickering sprites–, but that’s probably not necessary as it works in real hardware and most emulators using 48K models.