Magic Disk 64

home to index to text: MD9407-KURSE-IRQ-KURS_9.2.txt
      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!)

Valid HTML 4.0 Transitional Valid CSS!