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)