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 Inhal- te der Einschalt-Register des Pseudo- VICs aufaddiert, wobei das Ergebnis die Anzahl eingeschalteter Sprites ergibt (wenn an, dann Wert=1, sonst 0). An- schließ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 einge- schaltet, so wird auch nur diese Anzahl aktiviert werden. Sind es mehr, so müs- sen immer alle acht eingeschaltet wer- den. Der so ermittelte Wert wird dann in der Speicherzelle $7F zwischengespei- chert. Für den weiteren Verlauf der Rou- tine ist auch die ermittelte Anzahl not- wendig, die in der Zeropageadresse $7E 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 einzel- nen Koordinaten manipulieren muß, ist diese Maßnahme notwendig. Desweiteren darf sie die Spritedaten in den Regi- stern 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 Reihen- folge, 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 sor- tierenden Feld angewandt wird, wie Spri- tes eingeschaltet sind. Er ermittelt durch eine Reihe von Vergleichen immer den kleinsten Wert innerhalb der Kopie der Y-Positionen, legt ihn in der Zei- ger-Liste bei $F0 ab, und setzt die ent- sprechende 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 eben- falls 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 Spri- tes aus den entsprechenden Registern holen, und in den VIC schreiben. Bevor wir nun zur Beschreibung der Rou- tine kommen, noch einige technische Hin- weise: damit die Routine möglichst schnell arbeitet, wurde auf die Verwen- dung 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 Mini- mum vergleicht. Analog ist es bei ande- ren Vorgängen (so auch bei der Aufaddie- rung der Sprite-Einschalt-Register oben, wo eigentlich auch eine Schleife hätte benutzt werden können). Dadurch verlän- gert sich das Programm natürlich ein wenig, jedoch ist der damit verbundene Speicheraufwand noch erträglich und wir erreichen zudem eine hohe Verarbeitungs- geschwindigkeit. Da sich viele Vorgänge oft wiederholen, werde ich an diesen Stellen mit "..." eine Folge andeuten, die analog auch für alle weiteren Spri- tes 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 einge- schaltet sind. Hier wird nun geprüft, ob ein Sprite eingeschaltet ist, oder nicht, und in letzterem Fall die Y- Koordinate mit dem Wert $FF überschrie- ben, damit das abgeschaltete Sprite bei der Sortierung später nicht mehr berück- sichtigt 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 $8F ;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 ein- geschalteten 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 Sor- tierung 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 entspre- chende Sprite geladen, der der Sprite- nummer entspricht. Das X-Register enthält den aktuellen Index auf die Zei- gertabelle und wird pro Durchlauf um eins erhöht. Die Sortierung ist beendet, wenn die Vergleichsroutine insgesamt so oft durchlaufen wurde, wie Sprites ein- geschaltet 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 ermittel- ten Minimum der entsprechende Y-Wert auf $FF gesetzt, damit er im nächsten Ver- gleich nicht mehr herangezogen wird. Auf diese Weise wird nun nach und nach immer wieder der kleinste Wert ermittelt, so- lange, bis der Puffer nur noch $FF-Werte enthält, und die Schleife "Anzahl- Sprites"-Mal durchlaufen wurde. Die Sor- tierung ist damit beendet, und die Mul- tiplex-Routine kehrt wieder zur IRQ- Routine zurück. Hier nun wird als Nächstes die "SETSPR"- Routine aufgerufen, die anhand der er- mittelten 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 anzuzei- gen, und setzt den nächsten IRQ-Auslöser entsprechend. Zunächst einmal wollen wir uns den ersten Teil dieser Routine an- schauen. 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 ein- sta $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 Spri- te0 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 Ra- ster-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 Routi- ne "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 entsprechen- de Sprite zuständig ist. Durch das Ein- odern 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 Inter- rupt 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 di- rekt auf die Routine zur Darstellung des nächsten Sprites verzweigt (sh. BEQ- bzw. BMI-Befehle). Diese beginnt dann folgendermaßen: (Bitte wählen Sie nun den 3.Teil des IRQ-Kurses aus dem Textmenu!)