16-FARBEN-SCROLLING
Teil II
Im ersten Teil dieses Kurses wurde viel
Theoretisches gesagt. Um nun langsam (!)
zur Praxis zu kommen, wollen wird dies-
mal anhand eines Source-Codes, welchen
ich ausführlich dokumentieren und er-
klären werde, einen Schritt in Richtung
"Linecrunching", so wie's wirklich
funktioniert, machen.
Der "Linecrunching-Sourcecode" als
sequentielles File, wurde nur wenig
dokumentiert. Doch sollte er für jeden,
der folgendes Kapitel "durchackert" hat,
leicht verständlich sein.
Um kurz zu wiederholen:
Horizontale Bildverschiebung erreichen
wir, indem wir den VIC dazu bringen,
eine neue Cursor-Zeile aufzubauen, ob-
wohl die alte noch nicht abgeschlossen
wurde und der darstellende Elektronen-
Strahl gerade am sichtbaren Bereich
unterwegs ist. So läßt sich durch Timing
eine X-Versetzung auf Cursor-Positionen
genau erreichen.
Vertikal dupliziert sich die Trickserei!
Und zwar simuliert man für den oberen
Screen-Bereich beliebig viel Cursor-
Zeilen, dir nur eine Rasterzeile hoch
sich (Verhältnis 1:8). So werden die
Zeilen "geschrumpft" und der Bildscirm-
Inhalt nach oben gezogen. Was oben
fehlt kommt (so wie beim Horizontalen
Versetzen rechts) unten wieder rein.
Gut, nun wird's ernst. Sehn' wir uns die
Sache mit dem $d011-Register genauer an:
lda #$1b ;Register
sta $d011 ;reinitialisieren
lda #$2c
fl1 cmp $d012 ;auf Rasterzeile #$2c
bne fl1
ldx #4
fl2 dex ;extaktes Timing
bne fl2
Bis jetzt wurde nur $d011 richtig-
gesetzt (muß jeden Rasterdurchlauf re-
initialisiert werden!) und auf die
entsprechende Raster-Y-Position ge-
wartet (hängt ja bekanntlich vom
3-Bit-Wert im $d011-Register ab. Die
Schleife danach bringt nur genaueres
Timing, damit der Rasterstrahl in etwa
dort ist, wo wir ihn brauchen (dieses
Timing ist noch nicht auf Zyklen
ganau; die Schwankungen liegen von 1-4
Taktzyklen).
fl4 lda dtab,x ;$d011-Wert-Tabelle
dec $d02f ;$d02f(?) erniedrigen
sta $d011 ;Wert schreiben
inc $d02f ;(?) wieder erhöhen
nop ;Timing-nops!
.
. u.s.w.
.
Zuerst wird der entsprechende $d011-Wert
aus einer angelegten Tabelle (kommt
noch!) ausgelesen. Danach folgt wieder
was typisch "C64-Mäßiges": Bevor nun
der ausgelesene Wert ins $d011-Reg. ge-
schrieben wird, erniedrigen wir das
Register $d02f, um es danach wieder zu
erhöhen. Rein sinnlos, oder ? Doch wer
die beiden "Sinnlosigkeiten" aus dem
Code entfernt, wird sich wundern: Kein
Linecrunching ohne $d02f! Warum ? Wer
den C64 so lange und gut kennt wie ich,
fragt so was nicht. Er wundert sich gar
nicht mal.
Danach kommt wieder Rasterzeitfüllendes
Timen. Erwähnt sei, daß ein NOP-Befehl
genau 2 Taktzyklen benötigt, wohingegen
ein BIT $XX-Befehl 3 braucht. So läßt
sich auf Zyklen genau verzögern. Ein
entsprechendes Beispiel finden wir
später beim X-Scrollen, da wir dort
den Rasterstrahl ja an jeder möglichen
X-Position austricksen werden.
.
. ...und weiter:
.
inx ;Pointer erhöhen
up cpx #2 ;fertig ?
bne fl4
fll4 lda dtab+1,x ;aus Tabelle+#1
dec $d02f ;wie gehabt
sta $d011
inc $d02f
nop
.
.
.
Ab "fll4" passiert anscheinend ganau
dasselbe wie zuvor, doch: Wir lesen den
$d011-Wert aus der Tabelle+1. Warum ?
Folgende Rasterzeilen wird sozusagen
nur Zeit verbraucht, um die Lücke zu
füllen. Die Lückenspanne ist linear zur
Y-Versetzung. Wenn viele Zeilen "ge-
staucht" werden, ist die Spanne klein -
und umgekehrt. Und dadurch, daß wir
aus der Tabelle+1 lesen, passiert gar
nichts. Allerdings müssen wir in $d011
etwas schreiben, da wir sonst mit dem
Soft-Scrolling in Y-Richtung nicht
zurechtkommen.
.
. ...und weiter:
.
inx ;Pointer erhöhen
cpx #28 ;Zeilen-Limit ?
bne fll4 ;zurück!
ldx #1
fl5 dex ;wieder timen...
bne fl5
lda #$59 ;Neuer Fix-Wert für
sta $d011 ;$d011
ldx #$4f ;x-Reg.für Raster-Check
lda #$5f ;$d011-Wert in Akku
fl6 cpx $d012 ;Rasterzeile schon
bne fl6 ;erreicht ?
ldx #3
fl7 dex ;und wieder timen...
bne fl7
sta $d011 ;jetzt in $d011!
Linecrunching ist abgeschlossen (max.
28 Rasters!) und zwischen den gewohnten
"Austimereien" wurde der Fixwert #$59
in $d011 geschrieben und anschließend
nochmal #$5f. Das war die Vorbereitung
für das X-Scrolling, dem jetzt nichts
mehr im Wege steht...
lda #208 ;Border eng
ora xsoft ;mit Xsoft verknüpft
sta 53270 ;ins X-Scroll-Register
lda #$0f
sta $d02f ;$d02f zurücksetzen
ldx #3
jumpi dex ;zum Xten mal Timen
bne jumpi
Alles ist nun vorbereitet: "Softscroll-
3-Bit-Byte" (0-7) verknüpft, $d02f
re-initialisiert (für nächsten Durchlauf
notwendig!) und wieder Verzögerung.
Warum zwei mal ins $d011-Reg.geschrieben
wird, ist auch ganz einfach: Durch diese
besondere Zusammensetzung der beiden
Werte und auch dem Zeitraum zwischen
den beiden, erreichen wir, daß der
Prozessor nun den nächsten Befehl auf
einer fixen Raster-X-Position durch-
führt. D.h. das relativ ungenaue (auf
1-4 Zyklen genaue) Timing ist jetzt
auf 1 Taktzyklus genau. Und genau das
ist absolut notwendig für den X-Trick,
für das sog. Hard-Scrolling...
lda #$59 ;Wert für $d011
xhard bne jumpl+0 ;variabler jump
jumpl cmp #$c9 ;22 x "cmp #$c9"
cmp #$c9
.
.
cmp #$c9
bit $ea ;versteckter "NOP"
sta $d011
Im Grunde genommen sind diese 22 platz-
füllend wirkenden "cmp #$c9" wieder
Timer-Elemente. Und zwar ist der Jump-
BNE-Befehl ausschlaggebend: Gesprungen
wird immer (da #$59 ja unequal 0 ist!),
und zwar soweit, wie wir in xhard+1
schreiben. Logische Werte wären 0-39,
da wir eine Cursor-Auflösung von 40
Zeichen haben.
Wir wissen, daß wir eine Verzögerung von
0-39 Taktzyklen brauchen, um den Screen
an alle möglichen Position versetzen zu
können. Genau das erledigen die "CMP
#$c9" Befehle. Wenn wir uns den Opcode
dieses Befehls in einem Monitor ansehen,
merken wir, daß der Opcode des Befehls
"CMP" den Wert #$c9 besitzt. Das heißt,
wir können den BNE-Jump irgendwo in die
Liste steuern, und der Prozessor wird
immer "CMP #$c9" entdecken, egal ob
der Wert nach dem "BNE" (xhard+1) gerade
oder ungerade ist.
Schließlich wird die "CMP #$c9"-Liste
noch mit einem "BIT #$ea" abgeschlossen.
Erraten! #$ea ist der Opcode-Wert für
"NOP". Nun liest der Prozessor je nach-
dem, ob es eine gerade oder ungerade
Sprungadresse ist, folgende Befehls-
Serien...
gerade: ungerade:
... ...
cmp #$c9 cmp #$c9
cmp #$c9 cmp #$c9
cmp #$c9 cmp #$24 ;#$24 = Opcode
bit #$ea nop für BIT
Diese Lösung scheint auf den ersten
Blick vielleicht ein wenig aufwendig
und kompliziert, doch wenn man das
Prinzip und die Problemstellung richtig
verstanden hat, so entdeckt man einen
kleinen Touch von Genialität dahinter.
Gut, auch diesmal war wieder 'ne Menge
"Stuff" dabei, der Köpfe zum Rauchen
bringen kann. Doch Assembler-Tüftler
werden mit Hilfe des Source-Files
bald den Durchblick haben.
Der HAMMER und die KRONE im Teil 3:
Scroll-Action pur für eigene Projekte
und ein super Editor für bunte,
riesengroße Scrollfields...
(hs/wk)