Fortsetzung IRQ-Kurs (Teil9)
Wie Sie sehen, wird hier lediglich der
Rasterinterrupt auf Zeile $ E0 gesetzt, sowie die Bord-Routine als IRQ-Routine
eingestellt. Die eigentliche Arbeit wird
von den Routinen " PLEX" und " SETSPR" durchgeführt, wovon wir uns die Erstere
nun genauer anschauen möchten. Sie steht
ab Adresse $1300 :
PLEX:clc ;Sprites zaehlen, indem lda $80 ; die Inhalte der Ein-/ adc $81 ; Ausschaltregister adc $82 ; aller Sprites einfach adc $83 ; im Akku aufaddiert adc $84 ; werden. adc $85 adc $86 adc $87 adc $88 adc $89 adc $8a adc $8b adc $8c adc $8d adc $8e adc $8f sta $7e ;Anzahl merken tax ;Aus Tabelle ONTAB lda ONTAB,x; den VIC-Wert zum Ein- sta $7f ; schalten holen und in ; $7F ablegen cpx #$00 ;Keine Sprites an? bne clry ;Nein, also weiter rts ;Sonst Prg. beenden
Diese Routine ermittelt zunächst einmal, wieviele Sprites überhaupt eingeschaltet
sind. Dies tut sie, indem Sie die Inhalte der Einschalt-Register des Pseudo-VICs aufaddiert, wobei das Ergebnis die
Anzahl eingeschalteter Sprites ergibt
( wenn an, dann Wert=1, sonst 0) . Anschließend wird aus der Tabelle " ONTAB" der Wert ausgelesen, der in das VIC- Register zum Einschalten der Sprites
kommen muß, um die gefundene Anzahl
Sprites zu aktivieren. Die Liste enthält
folgende Werte:
$00,$01,$03,$07,$0F,$1F,$3F,$7F $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
Sind nun weniger als acht Sprites eingeschaltet, so wird auch nur diese Anzahl
aktiviert werden. Sind es mehr, so müssen immer alle acht eingeschaltet werden. Der so ermittelte Wert wird dann in
der Speicherzelle $7 F zwischengespeichert. Für den weiteren Verlauf der Routine ist auch die ermittelte Anzahl notwendig, die in der Zeropageadresse $7 E
untergebracht wird. Zum Schluß prüft die
Routine noch, ob überhaupt ein Sprite
eingeschaltet werden soll, und kehrt bei
einer Anzahl von 0 unverrichteter Dinge
zur IRQ-Routine zurück.
Wenn mindestens ein Sprite eingeschaltet ist, so wird die eigentliche Multiplex-Routine aktiv. Sie muß nun die Y-Koordinaten der Sprites sortieren, und
die Verteilung der 16 virtuellen Sprites
auf die echten VIC-Sprites übernehmen.
Zur Sortierung benötigen wir noch zwei
weitere, jeweils 16 Byte große, Felder.
Im ersten, von Zeropageadresse $ E0 bis
$ EF, wird eine Kopie der Y-Koordinaten
angelegt, welche dann sortiert wird. Da
die Sortierroutine die Werte der einzelnen Koordinaten manipulieren muß, ist
diese Maßnahme notwendig. Desweiteren
darf sie die Spritedaten in den Registern von $80 bis $ DF nicht verändern, bzw. vertauschen, da ein Programm, das
die viruellen VIC-Register mit Daten
füttert, ja immer davon ausgehen muß, daß es bei Sprite0 immer das ein und
selbe Sprite anspricht, und nicht eines, das von einer Position weiter hinten
hierhin sortiert wurde. Deshalb sortiert
die Multiplex-Routine nicht die eigent- lichen Sprites in die benötigte Reihenfolge, sondern legt eine Tabelle mit
Zeigern auf die Reihenfolge der Sprites
an. Selbige wird in den Zeropageadressen
von $ F0 bis $ FF abgelegt. Sie enthält
nach der Sortierung Werte zwischen 0 und
15, die als Index auf die dem virtuellen
Sprite entsprechenden Register dienen.
Zur eigentlichen Sortierung verwenden
wir nun einen ganz einfachen Bubblesort-Algorithmus, der so oft über dem zu sortierenden Feld angewandt wird, wie Sprites eingeschaltet sind. Er ermittelt
durch eine Reihe von Vergleichen immer
den kleinsten Wert innerhalb der Kopie
der Y-Positionen, legt ihn in der Zeiger- Liste bei $ F0 ab, und setzt die entsprechende Koordinate auf $ FF, damit sie
im nächsten Sortierdurchlauf den
größtmöglichen Wert enthält und somit
nicht noch einmal das Minimum sein kann.
Ebenso müssen wir mit den Y-Koordinaten von abgeschalteten Sprites verfahren, die vor dem eigentlichen Sortieren ebenfalls auf $ FF gesetzt werden. Dadurch
stehen sie immer am Ende der Liste. Nach
der Sortierung kann die Routine dann mit
Hilfe der Zeiger die Daten zu den Sprites aus den entsprechenden Registern
holen, und in den VIC schreiben.
Bevor wir nun zur Beschreibung der Routine kommen, noch einige technische Hinweise: damit die Routine möglichst
schnell arbeitet, wurde auf die Verwendung von Schleifen so weit wie möglich
verzichtet. Das heißt, daß z. B jeder der
Y-Werte über einen eigenen CMP-Befehl
verfügt, der ihn mit dem aktuellen Minimum vergleicht. Analog ist es bei anderen Vorgängen ( so auch bei der Aufaddierung der Sprite-Einschalt- Register oben, wo eigentlich auch eine Schleife hätte
benutzt werden können) . Dadurch verlängert sich das Programm natürlich ein
wenig, jedoch ist der damit verbundene Speicheraufwand noch erträglich und wir
erreichen zudem eine hohe Verarbeitungsgeschwindigkeit. Da sich viele Vorgänge
oft wiederholen, werde ich an diesen
Stellen mit " . . ." eine Folge andeuten, die analog auch für alle weiteren Sprites ausgeführt wird.
Kommen wir nun also zur eigentlichen
Multiplex-Routine, die mit dem Label
" CLRY" beginnt, wohin auch die letzte
Routine verzweigt, wenn Sprites eingeschaltet sind. Hier wird nun geprüft, ob
ein Sprite eingeschaltet ist, oder
nicht, und in letzterem Fall die Y-Koordinate mit dem Wert $ FF überschrieben, damit das abgeschaltete Sprite bei
der Sortierung später nicht mehr berücksichtigt wird:
CLRY:ldy #$ff ;Y-Reg mit $FF laden sx0: lda $80 ;Sprite0 an? bne sx1 ;Ja, also weiter sty $B0 ;Nein, also $FF in Y-Pos sx1: lda $81 ;Sprite 1 an? bne sx2 ;Ja, also weozer sty $B1 ;Nein, also $FF in Y-Pos
sx2 : . . . Analog für Sprites 3-14
sx15 : lda $8 F ; Sprite 15 an?
bne YCOPY ; Ja, also zu YCOPY spr.
sty $ BF ; Nein, also $ FF in Y-Pos Damit hätten wir nun also alle Y-Posi- tionen abgeschalteter Sprites gelöscht.
Da die Routine die Y-Positionen der eingeschalteten Sprites nicht verändern
darf, wird nun eine Kopie dieser Werte
in $ E0 bis $ EF angelegt:
YCOPY: lda $ B0 ; Y-Wert Sprite 0 sta $ E0 ; kopieren
. . . Analog für Sprites 1-14
lda $ BF ; Y-Wert Sprite 15
sta $ EF ; kopieren
Nachdem nun auch das Sorierfeld angelegt
wurde, können wir endlich mit der Sortierung beginnen. Hierbei benutzen wir
das Y-Register um den momentan kleinsten
Y-Wert zu speichern. Der Akku wird beim
Auffinden eines minimalen Wertes dann
immer mit einem Zeiger auf das entsprechende Sprite geladen, der der Spritenummer entspricht. Das X-Register
enthält den aktuellen Index auf die Zeigertabelle und wird pro Durchlauf um
eins erhöht. Die Sortierung ist beendet, wenn die Vergleichsroutine insgesamt so
oft durchlaufen wurde, wie Sprites eingeschaltet sind:
SORT: ldx #$00 ;Index init. loop: ldy #$ff ;YMin. init. cpy $E0 ;YSpr0 < YMin? bcc s1 ;Nein, also weiter ldy $E0 ;Ja, also YMin=YSpr0
lda #$00 ; Zeiger Spr0=0 laden
s1: cpy $E1 ;YSpr1 < YMin? bcc s2 ;Nein, also weiter ldy $E1 ;Ja, also YMin=YSpr1 lda #$01 ;Zeiger Spr1=1 laden
s2 : . . . Analog für Sprites 3-14
s15: cpy $EF ;YSpr15 < YMin? bcc s16 ;Nein, also weiter ldy $EF ;Ja, also YMin=YSpr15 lda #$0F ;Zeiger Spr15=15 s16: sta $F0,x ;Zeiger ablegen tay ;Zgr. als Index in Y-Reg lda #$ff ;YSpr mit YMin auf $FF sta $E0,y ; setzen. inx ;Zeiger-Index+1 cpx $7E ;mit Anzahl Sprites vgl. beq end ;Gleich, also Ende jmp loop ;Sonst nochmal sortieren
end: rts
Wie Sie sehen, wird nach jedem ermittelten Minimum der entsprechende Y-Wert auf
$ FF gesetzt, damit er im nächsten Vergleich nicht mehr herangezogen wird. Auf
diese Weise wird nun nach und nach immer
wieder der kleinste Wert ermittelt, solange, bis der Puffer nur noch $ FF-Werte
enthält, und die Schleife " Anzahl-Sprites"- Mal durchlaufen wurde. Die Sortierung ist damit beendet, und die Multiplex- Routine kehrt wieder zur IRQ-Routine zurück.
Hier nun wird als Nächstes die " SETSPR"- Routine aufgerufen, die anhand der ermittelten Werte zunächst die Daten der
ersten acht virtuellen Sprites in den
VIC überträgt. Gleichzeitig berechnet
sie mit Hilfe der Y-Position dieser
Sprites die Rasterzeile, an der ein IRQ
ausgelöst werden muß, um das jeweils
achte Sprite nach dem aktuellen anzuzeigen, und setzt den nächsten IRQ-Auslöser
entsprechend. Zunächst einmal wollen wir uns den ersten Teil dieser Routine anschauen. Er beginnt ab Adresse $1500 :
SETSPR:
lda $7F ;VIC-Wert für eingesch. sta $d015 ; Sprites setzen lda #$00 ;High-Bits der X-Pos sta $d010 ; löschen lda $7E ;Anzahl Sprites holen cmp #$01 ;Wenn mind. 1 Spr. ein- bcs spr00 ;gesch., dann weiter rts ;Sonst Ende spr00:clc ;C-Bit für Add. löschen ldx $E0 ;Zgr. aus Tabelle holen lda $90,x ;X-Pos. holen und für sta $d000 ; VIC-Spr.0 setzen lda $B0,x ;Y-Pos. holen und für sta $d001 ; VIC-Spr.0 setzen adc #22 ;Raster für Spr.Ende= sta ras8+1 ; YPos+22 setzen lda $D0,x ;Spr.Zeiger holen und sta $07f8 ; für VIC-Spr.0 setzen lda $C0,x ;Spr.Farbe holen und sta $d027 ; für VIC-Spr.0 setzen ldy $a0,x ;X-Pos-High holen, lda $d010 ;X-High-Bit-Reg. holen ora high0,y;Wert f. VIC-Spr.0 ein- sta $d010 ; odern und zurückschr. lda $7E ;Anzahl holen cmp #$02 ; Mehr als 1 Spr. an? bcs spr01 ; Ja, also weiter. rts ;Sonst Ende
spr01 : Analog für Sprite 1-7
. . .
spr07:... ;Werte f. Spr.7 setzen lda $7E ;Falls mehr als acht cmp #$09 ; Sprites, dann bcs acht ; neuen IRQ setzen rts ; Sonst Ende
acht: lda #< spr08 ; Adr. Routine " Spr8" sta $ fffe ; in IRQ-Vektor bei lda #> spr08 ;$ FFFE/$ FFF einsta $ ffff ; tragen ras8 : lda #$00 ; Rasterz. Spr0+22 als
sta $d012 ; IRQ-Quelle setzen dec $d019 ;VIC-IRQs freigeben rts ;Ende
Wie Sie sehen, holt die SETSPR-Routine
nun nacheinander alle Zeiger aus der
sortierten Tabelle bei $ F0 und überträgt
die Werte der ersten acht Sprites in den
VIC. Auf die Register des Pseudo-VICs
wird dabei über X-Register- indizierte
Adressierung zugegriffen. Zwei Dinge
sollten nun noch erläutert werden: Zum
Einen wird nach Setzen der Y-Position
eines Sprites die Rasterzeile berechnet, an der es zu Ende gezeichnet ist. Der so
ermittelte Wert wird nun an dem Label
" RAS8" plus 1 eingetragen, womit wir den
Operanden des LDA-Befehls am Ende der
Routine modifizieren. Er lädt nun den
Akku mit der besagten Rasterzeilennummer
und schreibt ihn in das Raster-IRQ- Register, womit der VIC nachdem er Sprite0 auf dem Bildschirm dargestellt hat, einen Raster-IRQ erzeugt. Hierbei wird dann zur Routine " SPR8" verzweigt, die
ich Ihnen gleich erläutern werde. Analog
wird mit den Sprites von 1-7 verfahren, wobei nach jedem Sprite geprüft wird, ob
noch ein weiteres Sprite eingeschaltet
ist, und demnach initialisiert werden
muß. Ist das nicht der Fall, so wird
direkt zum IRQ zurückgekehrt. Dadurch
wird ebenfalls nur dann ein IRQ auf die
Routine " SPR8" eingestellt, wenn
tatsächlich mehr als acht virtuelle
Sprites eingeschaltet sind. Im anderen
Fall brauchen wir ja keine Manipulation
vorzunehmen, weswegen der nächste Raster- IRQ, wieder auf " BORD" springt, wo
wir die Multiplex-Routine ein weiteres
Mal durchlaufen. Sollen nun aber mehr
als acht Sprites dargestellt werden, so
wird ein IRQ erzeugt, der auf die Routine " SPR08" springt, die wir uns gleich
näher anschauen werden.
Die zweite, etwas undurchsichtige Stelle
ist das Setzen der High-Bits für die X-Position eines Sprites. Hier gehen wir
wiefolgt vor: Zunächst wird der High-Wert der X-Position geholt, der nur 0 oder 1 sein, je nach dem ob die X-Position kleiner oder größer/ gleich 256 ist. Dieser Wert wird nun als Zeiger auf
eine Tabelle mit X-High- Bit-Werten für
das jeweilige Sprite benutzt. Sie steht
ganz am Ende des Programms und sieht
folgendermaßen aus:
high0: $00,$01 high1: $00,$02 high2: $00,$04 high3: $00,$08 high4: $00,$10 high5: $00,$20 high6: $00,$40 high7: $00,$80
Wie Sie sehen, enthält sie für jedes
Sprite einmal ein Nullbyte, das durch
die SETSPR-Routine geladen wird, wenn
der X-High- Wert Null ist, sowie einen Wert, in dem das Bit gesetzt ist, das im
X-High- Bit-Register für das entsprechende Sprite zuständig ist. Durch das Einodern des ermittelten Wertes in dieses
Register setzen wir nun letztendlich das
High-Bit der X-Position eines Sprites.
Hierbei wird bei den Sprites von 1-7 jeweils auf ein eigenes " High"- Label
zugegriffen, bei Sprite 1 also auf
" High1", bei Sprite 2 auf " High2" und so
weiter.
Kommen wir nun jedoch zur " SPR08"- Routine, die als IRQ-Routine aufgerufen
wird, und zwar nachdem Sprite0 auf dem
Bildschirm dargestellt wurde:
SPR08:pha ;Prozessor-Regs. retten txa pha tya pha
ldx $ F8 ; Zgr. aus Sort-Liste
lda $90,x ;X-Pos. in VIC-Spr0 sta $d000 ; eintragen lda $B0,x ;Y-Pos. in VIC-Spr0 sta $d001 ; eintragen lda $D0,x ;Spr.Zgr. in VIC-Spr0 sta $07f8 ; eintragen lda $C0,x ;Spr.Farbe in VIC-Spr0 sta $d027 ; eintragen ldy $90,x ;X-High-Wert holen lda $d010 ;VIC-High-Reg. lesen and #$FE ;Bit f. Spr0 ausmask. ora high0,y;Mit Wert f. X-High sta $d010 ; odern u. zurückschr. lda #<spr09;Adresse f. Raster-IRQ sta $fffe ; des nächsten Sprite lda #>spr09; in IRQ-Vektoren sta $ffff ; schreiben dec $d019 ;VIC-IRQs freigeben lda $7E ;Anzahl holen, cmp #$0a ;Spr9 benutzt? bcs ras9 ;Ja, also weiter jmp bordirq;Sonst IRQ rücksetzen ras9: lda #$00 ;Rasterz. f. Spr9 laden cmp $d012 ;m. akt. Strahlpos vgl. bmi direct9;Wenn größer o. gleich beq direct9; Spr.9 sofort zeichnen sta $d012 ;Sonst n. IRQ festlegen pla ;Prozessor-Regs zurück- tay ; holen und IRQ pla ; beenden tax pla rti
Wie Sie sehen, werden zunächst wieder
wie schon bei den anderen Sprite-Routinen, die vituellen VIC-Werte in den
echten VIC übertragen. Hiernach wird
verglichen, ob mehr als neun Sprites
dargestellt werden sollen, und wenn ja
zur Routine " RAS9" verzweigt, die den
IRQ für Sprite9 vorbeitet. An diesem
Label befindet sich wieder der LDA-Befehl, der von der Darstellungroutine
für Sprite1 so abgeändert wurde, daß er
die Rasterzeile des Endes dieses Sprites in den Akku lädt. Bevor nun der Interrupt gesetzt wird, prüft das Programm
durch einen Vergleich des Akkuinhalts
mit der aktuellen Rasterstrahlposition, ob der Rasterstrahl an besagter Zeile
nicht schon vorüber ist. In dem Fall
wird kein IRQ vorbereitet, sondern direkt auf die Routine zur Darstellung des
nächsten Sprites verzweigt ( sh. BEQbzw. BMI-Befehle) . Diese beginnt dann
folgendermaßen:
( Bitte wählen Sie nun den 3 . Teil des IRQ-Kurses aus dem Textmenu!)