At the end of my previous post, the spectrum was at the point where it would boot up and display the Sinclair copyright statement on the screen. Nothing else would happen and, indeed, you had to manually force the screen to refresh to get anything at all to display.
Since then, it has now been through two iterations of a screen refresh algorithm.
How the screen draws itself – part one
In the first iteration, I had the ULA attach itself to the CPU as a clock driven device (see the post about The Fetch Execute Loop). Once every 69,888 clock cycles (or t-states in Z80 terminology), it would ask the screen controller to invalidate its view. The view then redrew itself by asking the ULA for the pixels and attributes for the area of the screen to be redrawn (i.e. all of it in this case). This was a fine simple approach and quite fast since all of the drawing calculations were handled on a different thread to the CPU execution. However, there were disadvantages.
Firstly, the border effects achieved by rapidly switching the border colour were unachievable. The stripey effect you see when the tape is loading is caused by setting the border colour depending on the bits that arrive from the tape recorder and this happens many times in the time the video circuitry in a real Spectrum takes to draw the entire screen (1/50th of a second). With the approach above, the emulation effectively draws the entire screen instantaneously.
A further problem was caused by the keyboard logic. As well as asking the screen to draw itself every 69,888 cycles, the Spectrum also triggers the interrupt. This causes the Spectrum to run its interrupt routine, which, among other things, scans the keyboard. The keyboard interrupt routine scans the keyboard matrix and registers any new key presses or releases. If a key remains pressed for around 100 to 150 interrupts (two to three seconds on a real Spectrum), key repeating starts. Unfortunately, my Spectrum was still running at around about 50 times normal speed when built for release. So, if you kept the key pressed for more than about 1/20 second, you got repeat characters which made it unusable.
How the screen draws itself – part 2
In the current iteration, I have the ULA draw each line of the screen more or less at the correct time. The ULA’s clock driver is now called every 224 cycles. 224 cycles is how long it takes for the video circuitry to draw a complete line on the screen including the borders. The ULA doesn’t draw directly into the Cocoa view but to an array of
ScreenLines. Each pixel has a depth of four bits, three for colour and one for brightness. The
ScreenLine struct is effectively a packed array of four-bit values, representing the pixels on one line of the screen. This new method for drawing the screen greatly reduces the complexity of the view’s drawing, since all it has to do is ask for the value of each of the pixels it needs to draw and look them up in a colour palette to draw them.
There are several phases to drawing the screen with different timing.
- The ULA starts with period of doing nothing. This represents the end of the period during which the vertical retrace is happening on a real screen. In a real Spectrum, this takes the same amount of time as 16 screen lines. In the emulation, it is the same minus a small amount of time for the interrupt (see below).
- The ULA draws the top border. This is 48 lines.
- The ULA draws the screen content i.e. the lines with writing or graphics in them. There are 192 of these.
- The ULA draws another 48 lines of border.
- The ULA waits for another eight lines during what would be the first part of the vertical retrace.
- The ULA raises an interrupt. This has to last for a minimum of 23 t-states because that is how long the longest Z80 instruction takes. Any shorter and the processor might miss the interrupt.
This behaviour is coded as a simple finite state machine in which each state knows how many t-states to wait before the next time the ULA clock driver should be triggered (224 t-states by default) and how many times that state will be called before progressing to the next. e.g. 192 for the content state. Each state also has a set of three actions, one to be called the first time the state is called, one to be called when transitioning to the next state and one to be called for each call while the state is running. So for example, the DoInterrupt state has a start action that lowers the notInterrupt flag and an end action that raises it again, whereas the content state has a normal action that draws a line of the screen.
All of the above work happens synchronously on the CPU’s thread and this slows it down considerably. On my machine, it is down to 25 MHz. That’s slow enough to be able to use the keyboard – provided you are quick, but too fast for any Spectrum game.