Auf einem langsamen Prozessor wie dem Z80 ist es unerlässlich, über die Ausführungszeit nachzudenken. Oft ist ein sauberer Ansatz zu langsam, und man muss den Code optimieren, um ihn viel schneller zu machen.
Die ZX Spectrum Bildschirm-Bitmap ist nicht linear. Die 192 Pixelzeilen sind in drei Abschnitte von 64 Pixelzeilen unterteilt. In jedem dieser Abschnitte kommen zuerst alle 8 ersten Pixelzeilen, gefolgt von den zweiten Pixelzeilen und so weiter. Der Vorteil ist, dass man beim Schreiben von Zeichen in die Bitmap nur das H-Register inkrementieren muss, um die nächste Bitmap-Zeile zu erreichen. Der Nachteil ist, dass eine pixelgenaue Adressberechnung die Hölle ist.
So werden die Koordinaten eines Pixels auf die Adresse abgebildet:
| H | L | ||||||||||||||
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | 1 | 0 | Y7 | Y6 | Y2 | Y1 | Y0 | Y5 | Y4 | Y3 | X7 | X6 | X5 | X4 | X3 |
X2, X1 und X0 repräsentieren die Bitnummer an der Adresse. Sie können als Zähler für Rechts-Shift-Operationen verwendet werden.
Mein erster Versuch war ein geradliniger Code, der die Bitgruppen verschob, maskierte und an die richtigen Stellen bewegte. Er benötigte 117 Zyklen. Das ist nett, aber wir können es besser machen.
Wir brauchen viele Rotationsoperationen, um die Bits an die richtige Position zu schieben. Die Rotation ist eine ziemlich teure Operation auf einem Z80, da es keine Befehle gibt, die um mehr als ein Bit auf einmal rotieren. Meine Idee war, die X-Koordinate durch 8 zu teilen (indem ich sie dreimal nach rechts rotiere) und gleichzeitig Y3 bis Y5 in das L-Register zu schieben. Mit einem ähnlichen Trick konnte ich Bit 14 während des Rotierens setzen, was mir eine weitere or-Operation mit einer Konstanten ersparte.
Dies ist der finale optimierte Code. Er nimmt die X-Koordinate im C-Register und die Y-Koordinate im B-Register. Die Bildschirmadresse wird im HL-Registerpaar zurückgegeben. BC und DE bleiben unverändert, also gibt es keinen Bedarf für teure push- und pop-Operationen.
pixelAddress: ld a, b
and %00000111
ld h, a ; h enthält Y2-Y0
ld a, b
rra
scf ; Bit 14 setzen
rra
rra
ld l, a ; l enthält Y5-Y3
and %01011000
or h
ld h, a ; h ist jetzt komplett
ld a, c ; X durch 8 teilen
rr l ; und Y5-Y3 hineinrotieren
rra
rr l
rra
rr l
rra
ld l, a ; l ist jetzt komplett
ret
Er benötigt nur 108 Zyklen, inklusive ret. Die Optimierung hat mir 9 Zyklen (oder etwa 8%) gespart. Das klingt nicht nach viel, aber wenn der Code in einer Schleife aufgerufen wird, werden diese 9 Zyklen mit der Anzahl der Schleifendurchläufe multipliziert.
Ich behaupte, dies ist die schnellste Lösung, ohne auf eine Lookup-Tabelle zurückzugreifen. Versuch mich zu schlagen! 😁