Simon Owen wrote:
Simon Cooke wrote:
Interesting... Defender uses HPEN/LPEN... any idea how it
uses it?
It uses HPEN monitoring to track whether it's running late with the current
frame. I believe it still expects to be over the main screen area after
completing certain tasks, and if it's into the border it runs straight into
the next frame without waiting for the frame interrupt to be signalled in
the status port. The game uses the stack for lots of the drawing
code/lists, so runs with interrupts disabled at all times [you can confirm
that in the SimCoupe debugger by pressing U for 'execute until', and
entering 'ei' for the expression - the breakpoint will trigger next time
interrupts are enabled, which never happens].
Simon's explanation is correct. I will expand (from memory!) why I used this
method:
As we all know, the SAM is *seriously* underpowered. To ensure Defender ran at
a
constant (99.9% of the time!) 50-fps, all available horsepower needed to be
squeezed
from the old Z80! Often the fastest way to shunt data around is via the stack,
so I
used the stack to dump/erase sprites and also used a stack-based linked list
for the
world entities. The landscape also uses a stack-based dumper, this pops off a
value
which is added to the current landscape pixel position, moving the position
either up,
down or up/right, down/right. The mini-landscape used in the scanner is
processed
in a similar way. Even the 16 background stars are processed using a
stack-based
routine! Couple this with plenty of code unrolling, in-lining of graphics data
and also
screen-line interleaving of sprite dumping the game holds on to the magic
50-fps.
With so much stack activity occurring, often in crucial data areas, any
unexpected
interrupts would be a disaster. So Defender runs a constant DI state. This is
fine,
but introduces the problem of frame-syncing via the v-blank interrupt. Because
of
the DI state it is not possible for Defender to trap the v-blank interrupt.
Instead, a
simple polling loop on the status port has to be used. Again, this is fine if
you can
complete all in-game processing before the next v-blank. Defender does (just!)
for
99.9% of the time. This is where the problem lies. If you miss the v-blank,
but you
still wait in the status polling loop after all in-game processing, you
effectively see
the next v-blank and not the one you really wanted to see, because you missed
it!
This has the effect of dropping the game to 25-fps visually, and halves the
speed of
all sound effects. This is so very obvious to both eye and ear!!!
Unacceptable!
As Defender only just misses the frame v-blank when things are hectic on-screen
it was totally unforgivable to allow the game to stutter to half its speed,
ruining both
the game and its playability. The solution was not to wait if we had missed a
v-blank,
but simply continue processing the next game-loop as if we had just seen a
v-blank.
This is simple if you can set a flag on a v-blank interrupt. In Defender's
case this is
not possible, so an alternative method for detecting missed v-blanks was used.
Enter HPEN! Put simply, I read the HPEN value after processing approximately
50%
of the in-game functions (50% *never* misses v-blank) and then read it again
after all
in-game processing is done. Then a few calculations determine if we missed the
last
v-blank. If so, then don't wait for a v-blank and simply blast into the next
game loop.
The effect is a slight visual stutter on missed frames, and no real change in
the sound
effects. 100% preferable to the 25-fps visual drop and audible slowdown of all
effects!
In fact, the visual stutter is almost exactly the same as the real Defender
machine
when it, too, misses frames. The exception being that SAM Defender doesn't
bother
hyperspacing entities off-screen on missed frames, which the real Defender
does
when it trys to claw back some precious CPU time!! ;-)
Job done! :-)
Chris.