Erstellt: 1. Juli 2014 (zuletzt geändert: 26. Januar 2022)

VIC-II: Grafikmodes – FLI

Nun wirds noch bunter…

C64 Studio & AMCE

In den vorangegangenen Beiträgen haben wir die offiziellen Grafikmodes des C64 kennengelernt. Es gibt aber noch eine Reihe Software-Grafikmodi. Schauen wir uns doch hier mal einen ersten dieser benutzerdefinierten Modi an.

Ihr solltet bereits mit den Beiträgen VIC-II: Grafikmodes – BITMAP (Multi-Color), VIC-II: Speicherbereiche festlegen und Der Rasterzeileninterrupt vertraut sein und wissen, wie man den Oberen & unteren Rand öffnen kann!

Flexible Line Interpretation (FLI)

Bevor wir vorwärts, Richtung FLI gehen, werfen wir noch mal einen Blick zurück auf den MultiColor-Bitmap-Modus. Wie wir gesehen haben, bietet der MC-Modus durch Halbierung der Auflösung in X-Richtung, die Möglichkeit mehr Farben zu verwenden. Außerdem ist der Bildschirm trotz Bitmap-Modus immer noch in 40*25 Zellen (bzw. Zeichen) zu je 4*8 Pixeln unterteilt. Durch setzen der Bit-Paare innerhalb einer jeden 4*8-Pixel großen Zelle können wir eine von vier Farben wählen. Die folgende Tabelle sollte euch bekannt sein.

%00 - Hintergrundfarbe aus $d021
%01 - Farbe lt. oberen 4-Bits des Bildschirmspeichers
%10 - Farbe lt. unteren 4-Bits des Bildschirmspeichers
%11 - Farbe lt. Farb-RAM ab $d800

Der FLI-Modus ist vom Prinzip identisch mit dem normalen MultiColor-Bitmap-Modus, nur dass er es uns ermöglicht pro 4*8 Pixel-Zelle sämtliche 16 Farben zu verwenden.

Die 16 Farben des C64.
Die 16 Farben des C64.
Wie kommen wir an mehr Farben?

Da uns auch im FLI-Modus nur zwei Bits je Farbe zur Verfügung stehen, stellt sich die Frage, wie man so bis zu 16 verschiedene Farben je Zelle verwenden kann? Wir müssen zur Laufzeit die festgelegten Farben ändern und dort kommt Der Rasterzeileninterrupt ins Spiel. Das Ändern der Hintergrundfarbe würde uns niemals für jedes Zeichen gelingen, dies scheidet also somit schon mal aus. Da sich das Farb-RAM immer an der Adresse $d800 befindet, müssten wir für jede Rasterzeile, für jede Zeichen-Zeile, einen neuen Wert dorthin schreiben. Auch dies ist unmöglich! Bleibt also nur der Bildschirmspeicher!! Von dort wird im Bitmap-Modus bei %01 bzw. %10 bekanntlich die Farbe für das Pixel geholt. Wo ist hier nun aber der Unterschied zum Color-RAM? Müssten wir nicht auch hier für jede Rasterzeile erneut Farbwerte in den BS-Speicher schreiben?? Theoretisch schon, es gibt aber auch eine schnellere Lösung (Tipp VIC-II: Speicherbereiche festlegen).

In Etappen zum FLI-Bild

Um die Theorie etwas aufzulockern, entwickeln wir jetzt eine FLI-Anzeigefunktion. Beginnen wir damit, die Speicherbereiche festzulegen, den Bitmap-Modus zu aktivieren und richten noch einen stabilen Raster-IRQ ein. Wir wählen die VIC-Bank 1, also den Bereich von $4000 – $7fff für unser Beispiel. Dort legen wir zu Beginn (also bei $4000) direkt das SCREENRAM ab. Die Bitmap landet in den zweiten 8KB, beginnend bei $6000. Das Basis-Programm solltet ihr mittlerweile selbst hinbekommen, daher verzichte ich auf weitere Ausführungen.

Mit einer Bitmap beginnen

RASTER          = 45                ;hier den 1. Raster-IRQ auslösen
SCREENRAM       = $4000             ;BITMAP-Farben (wenn 'Pixel' %01 oder %10) 
BITMAPADDR      = $6000             ;Start der Bitmap
COLORRAM        = $d800             ;Beginn des Farb-RAMs


;*** Startadresse 
*=$0801
;** BASIC-Zeile: 2018 SYS 2062
 !word main-2, 2018 
 !byte $9e
 !text " 2062"
 !byte $00,$00,$00

!zone main
main
;*** Speicheraufteilung
 lda #%00000011                     ;Datenrichtung für Bit 0 & 1 des Port-A
 sta $dd02                          ;zum Schreiben freigeben
 lda #%00000010                     ;Bank-1
 sta $dd00                          ;auswählen
 lda $d018                          ;VIC-II Register 24 in den Akku holen
 and #%00001111                     ;das SCREENRAM in den ersten 1KB Block!
 ora #%00001000                     ;mit Bit-3 den Beginn des Bitmapspeichers
 sta $d018                          ;(hier den zweiten 8KB-Block) festlegen 

;*** MultiColor-Bitmap-Modus
 lda $d011                          ;VIC-II Register 17 in den Akku
 ora #%00100000                     ;Bitmap-Modus über Bit-5
 sta $d011                          ;einschalten
 lda $d016                          ;VIC-II Register 22 in den Akku
 ora #%00010000                     ;Multi-Color über Bit-4
 sta $d016                          ;aktivieren

;*** Rasterzeileninterrupt einrichten
 sei                                ;IRQs sperren
 lda #<MyIRQ                        ;Adresse unserer Routine in
 sta $0314                          ;den RAM-Vektor
 lda #>MyIRQ
 sta $0315
 lda #%00000001                     ;Raster-IRQs vom VIC-II aktivieren
 sta $d01a
 lda #RASTER                        ;Hier soll unsere Linie erscheinen
 sta $d012                      
 lda $d011                          ;Zur Sicherheit höchste Bit
 and #%01111111                     ;für die Rasterzeile löschen
 sta $d011
 lda #%01111111                     ;Timer-IRQs abschalten
 sta $dc0d
 lda $dc0d
 lda #%0000001                      ;evtl. aktiven Raster-IRQ bestätigen
 sta $d019
 cli                                ;Interrupts erlauben

 jmp *                              ;Endlosschleife



!zone MyIRQ 
;*** Raster-IRQ an Pagegrenze ausrichten, damit die Sprünge passen
!align 255,0

MyIRQ 
;*** Wenn wir hier landen, sind bereits 38-45 Taktzyklen
;*** in der aktuellen Rasterzeile (RASTER) vergangen!

;*** zweiten IRQ einrichten
;*** da die Zeit bei aktivierten ROM nicht reicht,
;*** können wir den 2. Raster-IRQ erst in der übernächsten Zeile bekommen
 lda #<DoubleIRQ                    ;(2 TZ) 2. Raster-IRQ einrichten
 sta $0314                          ;(4 TZ)
 lda #>DoubleIRQ                    ;(2 TZ)
 sta $0315                          ;(4 TZ)
 tsx                                ;(2 TZ) Stackpointer im X-Reg. retten
 stx DoubleIRQ+1                    ;(4 TZ) und fürs zurückholen sichern!
 nop                                ;(2 TZ)
 nop                                ;(2 TZ)
 nop                                ;(2 TZ)
 lda #%00000001                     ;(2 TZ) 1. Raster-IRQ später bestätigen
                                    ;------
                                    ;26 TZ

;*** Jetzt sind 64-71 Taktzyklen vergangen und wir sind
;*** aufjedenfall in nächsten Rasterzeile (RASTER+1)!
;*** Verbraucht wurden 1-8 TZ
 inc $d012                          ;(6 TZ) 2. IRQ in der übernächsten Zeile (RASTER+2)!!!
                                    ;       $D012 wurde bereits automatisch erhöht
 sta $d019                          ;(4 TZ) IRQ bestätigen
 cli                                ;(2 TZ) Interrupts für den 2. Raster-IRQ
                                    ;       wieder freigeben

;*** Wir befinden uns in Rasterzeile RASTER+1 und 
;*** haben bisher 13-20 Zyklen verbraucht

;*** etwas Zeit verschwenden...
 ldx #$08                           ;            2 TZ
.loop 
 dex                                ;8 * 2 TZ = 16 TZ
 bne .loop                          ;7 * 3 TZ = 21 TZ
                                    ;1 * 2 TZ =  2 TZ
                                    ;          ------
                                    ;           41 TZ

;*** Bis hier sind 54-61 Taktzyklen vergannen, jetzt auf den IRQ warten...
;*** Der nächste Rasterinterrupt wird während dieser NOPs auftreten!
 nop                                ;2 TZ (55)
 nop                                ;2 TZ (57)
 nop                                ;2 TZ (59)
 nop                                ;2 TZ (51)
 nop                                ;2 TZ (63)
 nop                                ;2 TZ (65)

DoubleIRQ
;*** Wir sind nun in Rasterzeile RASTER+2 und
;*** haben bisher genau 38 oder 39 Taktzyklen benötigt!!
;*** Wir können so sicher sein, da der IRQ während der NOPs auftrat.

;*** Jetzt exakt soviele Taktzyklen 'verschwenden', wie in 
;*** dieser Zeile noch zu verarbeiten sind (also 24 oder 25).
 ldx #$00                           ;(2 TZ) Platzhalter für 1. Stackpointer
 txs                                ;(2 TZ) Stackpointer vom 1. IRQ wiederherstellen
 nop                                ;(2 TZ)
 nop                                ;(2 TZ)
 nop                                ;(2 TZ)
 nop                                ;(2 TZ)
 bit $01                            ;(3 TZ)
 ldx $d012                          ;(4 TZ)
 lda #$01                           ;(2 TZ) weiß schonmal in den Akku
 cpx $d012                          ;(4 TZ) sind wir noch in Rasterzeile 22?
                                    ;------
                                    ;25 TZ = 63 oder 64 TZ!!!

 beq MyIRQMain                      ;(3 TZ) wenn JA einen letzten Takt 'verschwenden'
                                    ;(2 TZ) sonst einfach weiterlaufen...

!zone MyIRQMain
;*** Wir beginnen also immer exakt nach 3 TZ in der dritten Rasterzeile (RASTER+3)
;*** nach dem 1. Raster-IRQ (den hatten wir ja in für Zeile RASTER festgelegt)
MyIRQMain




;*** Aufräumarbeiten
 lda #<MyIRQ                        ;Original IRQ-Vektor setzen
 sta $0314
 lda #>MyIRQ
 sta $0315

 lda #RASTER                        ;Original Rasterzeile setzen
 sta $d012

 lda #%00000001                     ;IRQ bestätigen
 sta $d019

 jmp $ea81                          ;zum Ende des 'Timer-Interrupts' springen

Das Programm ist so zwar lauffähig, zeigt aber bisher nur ein zufälliges Bild, abhängig von der Speicherkonfiguration.

Unsere Vorbereitungen (VICE – Rahmen-Debuggen aktiv)
Unsere Vorbereitungen (VICE – Rahmen-Debuggen aktiv)

Nun wollen wir eine von uns definierte Musterausgabe generieren. Dazu löschen wir als Erstes die kompletten 16KB, der von uns gewählten VIC-II-Bank. Fügt die neue Funktion clearALL einfach direkt hinter der Zeile jmp * ;Endlosschleife ein.

!zone clearAll
clearALL
 ldx #64                            ;Seitenzähler | 64 * 256 Bytes = 16KB
 ldy #0                             ;Byte-Zähler 
 tya                                ;0 fürs Löschen in den Akku
.loop                               ;die 16KB-Bank beginnt mit dem BS-Speicher 
 sta SCREENRAM,Y                    ;löschen
 dey                                ;Byte-Zähler verringern
 bne .loop                          ;solange nicht 0 nochmal -> .loop
 inc .loop+2                        ;MSB von 'sta SCREENRAM,Y' erhöhen
 dex                                ;Seitenzähler verringern
 bne .loop                          ;solange nicht 0 nochmal -> .loop
.loop1                          
 sta COLORRAM,Y                     ;jetzt noch das COLORRAM löschen
 sta COLORRAM+256,Y
 sta COLORRAM+512,Y
 sta COLORRAM+744,Y
 dey                                ;Byte-Zähler verringern
 bne .loop1                         ;solange nicht 0 nochmal -> .loop1
 rts                                ;zurück zum Aufrufer

Wie ihr seht, ist die Schleife sehr einfach. Die Hauptschleife bei .loop setzt jedes einzelne Byte der 16KB VIC-Bank auf 0. Da die Bank direkt mit unserem Bildschirmspeicher bei SCREENRAM beginnt, starten wir mit dem Löschen dort. Nach je 256 Bytes, erhöhen wir dann das MSB der Adresse um eins, bis alle 64 Pages gelöscht wurden. Zum Schluß setzen wir bei .loop1 auch noch das COLOR-RAM auf 0.

Jetzt müssen wir nur noch die neue Funktion clearALL aufrufen, außerdem setzen wir noch die Rahmen- und Hintergrundfarbe und deaktivieren die Sprites. Fügt die folgenden Zeilen direkt vor jmp * ;Endlosschleife ein.

 lda #6                             ;dunkelblau
 sta $d020                          ;als Rahmenfarbe
 lda #0                             ;schwarz 
 sta $d021                          ;für den Hintergrund
 sta $d015                          ;zur Sicherheit alle Sprites deaktivieren

 jsr clearALL                       ;die komplette 16KB-VIC-II-Bank löschen

Jetzt sollte unsere Anzeige schon mal sauber sein…

Es wurde „alles“ gelöscht.
Es wurde „alles“ gelöscht.

Nun bringen wir noch unser Testmuster auf den Bildschirm. Wir zeichnen dazu drei Zeilen mit je vier Zeichen, die jeweils eine Farbkombination enthalten und eine Zeile die alle möglichen Kombinationen enthält. Fügt die nächste Funktion drawPattern hinter der von eben ein.

!zone drawPattern
drawPattern
 ldx #7                             ;Schleifenzähler | 8 Bytes je 'Zeichen'
.loop
 lda #%01010101                     ;Muster für die erste Zeile
 sta BITMAPADDR+80,X                ;4 'Zeichen'
 sta BITMAPADDR+88,X
 sta BITMAPADDR+96,X
 sta BITMAPADDR+104,X
 lda #%10101010                     ;Muster für die zweite Zeile
 sta BITMAPADDR+400,X               ;4 'Zeichen'
 sta BITMAPADDR+408,X
 sta BITMAPADDR+416,X
 sta BITMAPADDR+424,X
 lda #%11111111                     ;Muster für die dritte Zeile
 sta BITMAPADDR+720,X               ;4 'Zeichen'
 sta BITMAPADDR+728,X
 sta BITMAPADDR+736,X
 sta BITMAPADDR+744,X
 lda #%00011011                     ;Muster für die vierte und letzte Zeile
 sta BITMAPADDR+1040,X              ;4 'Zeichen'
 sta BITMAPADDR+1048,X
 sta BITMAPADDR+1056,X
 sta BITMAPADDR+1064,X
 dex                                ;Schleifenzähler verringern
 bpl .loop                          ;solange positiv nochmal -> .loop
 ldx #3                             ;Schleifenzähler Farben für 4 'Zeichen'
.loop1
 lda #%11000011                     ;Farbe grau und türkis
 sta SCREENRAM+10,X                 ;in den BS-Speicher
 sta SCREENRAM+50,X
 sta SCREENRAM+90,X
 sta SCREENRAM+130,X
 lda #7                             ;Gelb
 sta COLORRAM+10,X                  ;ins Farb-RAM
 sta COLORRAM+50,X
 sta COLORRAM+90,X
 sta COLORRAM+130,X
 dex                                ;Schleifenzähler verringern
 bpl .loop1                         ;solange positiv nochmal -> .loop1
 rts                                ;zurück zum Aufrufer

Die Funktion ist total simpel: Bei .loop zeichnen wir unser Testmuster und bei .loop1 setzen wir die benötigten Farben im Bildschirmspeicher und Farb-RAM.

Das Muster im Detail
Das Muster im Detail

Sobald ihr direkt hinter jsr clearALL ;die komplette 16KB-VIC-II-Bank löschen! die neue Funktion mit jsr drawPattern ;unser Muster zeichnen aufruft und das Programm startet, erscheint das Muster auf dem Bildschirm.

Die Bildschirmausgabe mit unserem Testmuster.
Die Bildschirmausgabe mit unserem Testmuster.

Auch wenn es nicht danach aussieht, dies ist nun unsere MultiColor-Bitmap, mit dem Testmuster. Wir sehen hier nochmal den uns bekannten Fakt, dass ein MC-Bild in einem 4*8 Pixel großem Feld (die Zapfen unten am Muster) nur vier verschiedene Farben aufweisen kann.

Um nun doch mehr Farben, durch das eingangs erwähnte Ändern der Farbinformationen aus dem Bildschirmspeicher zu erreichen, müssen wir noch mal einen Blick auf die Funktionsweise des VIC-II und die Rasterzeilen werfen:
Wie wir wissen gibt es normale Rasterzeilen und die sog. Bad Lines. Diese benötigt der VIC, um die Daten für den Ausgabebereich vorzubereiten. Das macht er immer zu Beginn einer neuen Zeichen-Zeile, also jede achte Rasterzeile vom Anfang des Ausgabebereichs an.

Hier können wir nun ansetzen:
Um mehr Farben zu erreichen, müssen wir selbst für noch mehr Bad Lines sorgen!
Nur dann liest der VIC die neuen Daten und unsere Ausgabe ändert sich. Wie bereits erwähnt, reicht die Zeit aber nicht um die 1000 Bytes des Bildschirmspeichers zu kopieren. Aber wir können in der Zeit die Adresse für den Bildschirmspeicher ändern! Durch dieses Vorgehen erhöht sich natürlich auch unser Speicherbedarf. Wie wir gleichen sehen, brauchen wir dann bis zu 7KB mehr!

OK, schauen wir uns das mal genauer an und sorgen dafür, dass unser Testbild bunter wird.

Für mehr Farben sorgen

Beginnen wir damit, hinter SCREENRAM sieben weitere Speicherbereiche mit den gewünschten Farben einzurichten. Unser Ziel (für diesen ersten Versuch) ist es, folgende Ausgabe mit unserem Muster zu erreichen.

So bunt soll das Muster werden.
So bunt soll das Muster werden.

Wir wollen im oberen Bereich alle 16 Farben untereinander darstellen (pro Zeichen also 8 verschiedene Farben) und unten (in den Zapfen) verwenden wir in jedem 4*8-Pixel großen Zeichen sogar alle 16 Farben aufeinmal.

Dazu benötigen wir acht verschiedene Bildschirmspeicher. Jeder dieser Speicher wird für eine Rasterzeile einer Zeichen-Zeile aktiviert. Werft einfach einen Blick aufs folgende Bild, ich hoffe dann wird es klarer.

Zuordnung der einzelnen Bildschirmspeicher (Screen-RAM)
Zuordnung der einzelnen Bildschirmspeicher (Screen-RAM)

Rechts steht immer welches Screen-RAM in der jeweiligen Zeile aktiv ist. Schreiben wir also eine kleine Funktion, die die benötigten Bildschirmspeicher mit den entsprechenden Farben anlegt.

!zone initColors
initColors
 ldy #0                             ;Schleifenzähler für die acht Farbbereiche
.loop
 ldx #3                             ;Schleifenzähler Farben für 4 'Zeichen'
 lda testColors,Y                   ;Farbe holen
.loop1
 sta SCREENRAM+10,X                 ;in den BS-Speicher
 sta SCREENRAM+50,X
 sta SCREENRAM+90,X
 sta SCREENRAM+130,X
 dex                                ;Schleifenzähler (Farben) verringern
 bpl .loop1                         ;solange positiv nochmal -> .loop1
 inc .loop1+2                       ;MSB für die zusätzlichen Bereiche erhöhen
 inc .loop1+2                       ;MSB für die zusätzlichen Bereiche erhöhen
 inc .loop1+2                       ;MSB für die zusätzlichen Bereiche erhöhen
 inc .loop1+2                       ;MSB für die zusätzlichen Bereiche erhöhen
 inc .loop1+5
 inc .loop1+5
 inc .loop1+5
 inc .loop1+5
 inc .loop1+8
 inc .loop1+8
 inc .loop1+8
 inc .loop1+8
 inc .loop1+11
 inc .loop1+11
 inc .loop1+11
 inc .loop1+11
 iny                                ;Schleifenzähler (Farbbereiche) verringern
 cpy #8
 bne .loop                          ;solange positiv nochmal -> .loop
 ldx #3                             ;Schleifenzähler Farben für 4 'Zeichen'
 lda #7                             ;Gelb
.loop2
 sta COLORRAM+10,X                  ;ins Farb-RAM
 sta COLORRAM+50,X
 sta COLORRAM+90,X
 sta COLORRAM+130,X
 dex                                ;Schleifenzähler verringern
 bpl .loop2                         ;solange positiv nochmal -> .loop2
 rts                                ;zurück zum Aufrufer

Wir gehen hier mit Y als Schleifenzähler, alle 8 Bildschirmspeicher durch. Dann holen wir uns aus der Tabelle testColors die benötigte Farbe und verwenden das X-Register um die vier Zeichen in einer Zeile zu füllen. Mit den sta SCREENRAM…,X Anweisungen füllen wir die vier Zeilen am Stück. Danach erhöhen wir das MSB aller sta SCREENRAM…,X-Anweisungen um 1024 (daher jeweils vier inc .loop1+…). Wurden alle acht Bildschirmspeicher vorbereitet, wird zum Schluß noch das Farb-RAM mit der Farbe gelb gefüllt und schon geht es zurück zum Aufrufer.

Löscht das bisherige Setzen der Farben aus drawPattern! Entfernt dafür alles nach bpl .loop ;solange positiv nochmal -> .loop und vor rts ;zurück zum Aufrufer.

Legt jetzt noch am Ende des Quellcodes die Tabelle testColors an.

testColors
 !byte %00001000, %00011001, %00101010, %00111011
 !byte %01001100, %01011101, %01101110, %01111111

Laßt uns initColors direkt vor jsr drawpattern ;unser Muster zeichnen mit jsr initColors ;die Farben setzen aufrufen. Startet ihr anschließend das Programm, dann sieht unsere Anzeige etwas anders aus.

Andere Farben.
Andere Farben.

In der Tabelle testColors sind jetzt andere Farben als weiter oben definiert, daher sieht unsere Anzeige nicht mehr wie eben aus.

Um gleich die Adresse für den Bildschirmspeicher schnell ändern zu können, sollten wir uns eine weitere Tabelle anlegen. Wir legen in dieser Tabelle direkt die für jede Rasterzeile benötigten Werte für das VIC-II-Register 24 $d018 ab. Dort wird bekanntlich über die Bits 7-4 festgelegt, wo sich der Bildschirmspeicher befinden soll. Wir lassen die Tabelle gleich vom Assembler bei der Programmerstellung anlegen.

Um die Taktzyklen besser unter Kontrolle zu haben, richten wir die Tabelle an der Pagegrenze aus.

!align 255,0
d018Values
 !for x = 0 to 31
     !for y = 0 to 7
         !byte %10000*y+%1000
     !end
 !end

Die Funktion zum Füllen ist wieder recht simpel. Wir legen in der Tabelle direkt die später benötigten Werte für $d018 ab. Wir nutzen dazu zwei !for-Schleifen, die vom Assembler bei der Programmerstellung ausgewertet werden, sie kosten uns also keine Zeit während der Programmausführung auf dem C64. Für $d018 benötigen wir im oberen Nibble die Position des Bildschirmspeichers und im unteren müssen wir daran denken die Speicherposition für die Bitmap zu setzen. Die innere y-Schleife, legt immer acht Bytes an, die die benötigten Werte für $d018 enthalten. Durch die äußere x-Schleife wiederholen wir das ganze 32 mal und füllen somit die komplette Page.

Ein Start würde nichts ändern, schließlich wird die Tabelle d018Values nur gefüllt, aber bisher nicht genutzt. Wollt ihr die Daten kontrollieren, dann könnt ihr direkt vor jmp * zum Testen mal die beiden folgenden Zeilen eingeben.

 lda d018Values+0
 sta $d018

Erhöht ihr den addierten Wert beim lda jetzt jedes Mal um 1 und startet das Programm, dann könnt ihr alle Farbkombinationen abrufen. Sobald ihr bei 7 angelangt seid, könnt ihr aufhören, danach geht es wieder von vorne los.

Die einzelnen Anzeigen durchs Umschalten des Bildschirmspeichers.
Die einzelnen Anzeigen durchs Umschalten des Bildschirmspeichers.

Vergesst nicht die beiden Zeilen wieder zu löschen!

Wie lösen wir eine Bad-Line aus?

Nun ist es an der Zeit, dass wir uns mal fragen, wie wir überhaupt eine Bad-Line auslösen? Eine Bad-Line tritt immer dann auf, wenn die unteren drei Bits der Rasterzeile mit den drei Bits für das vertikale Scrolling übereinstimmt! Wir können nun durch Veränderung der Scrollwerte, im VIC-Register 17 $d011, dafür sorgen, dass jede Rasterzeile zu einer Bad-Line wird! Auch hier sollten wir eine Tabelle, nennen wir sie d011Values 😉 , einsetzen. Fügt das Label ganz am Schluß, hinter dem d018Values-Block ein. Bytes brauchen wir diesmal nicht zu reservieren, hiernach kommt eh nichts mehr. Auch ein ALIGN 256 ist nicht nötig, da die Tabelle d018Values bereits an der Pagegrenze ausgelegt ist und sie exakt 256 Bytes belegt.

Wir können d011Values wieder ganz einfach vom Assembler füllen lassen:

d011Values
 !for x = 0 to 31
     !for y = 0 to 7
         !byte %111000 + y
     !end
 !end

Das erste FLI-Bild

Jetzt können wir im Raster-IRQ die Tabellen auslesen und für unser erstes FLI-Bild sorgen. Sucht das Label MyIRQMain und fügt direkt dahinter den folgenden Block ein.

MyIRQMain
;*** etwas Zeit vertrödeln...
 inc $d020                          ;(6 TZ) hier zur Kontrolle mal die Rahmenfarbe ändern
 dec $d020                          ;(6 TZ)
 bit $01                            ;(3 TZ)
 nop                                ;(2 TZ)
 ldx #0                             ;(2 TZ) Schleifenzähler für die Rasterzeilen

;*** der nächste Befehl wird direkt zu Beginn der 1. sichtbaren Zeile aufgerufen
.loop
 lda d018Values+1,X                 ;(4 TZ) Wert für $d018 aus der Tabelle holen
 sta $d018                          ;(4 TZ) und speichern
 lda d011Values+1,X                 ;(4 TZ) Wert für $d011 aus der Tabelle holen
 sta $d011                          ;(4 TZ) und speichern
 inx                                ;(2 TZ) Schleifenzähler erhöhen
 cpx #199                           ;(2 TZ) Wurde Zeile 199 erreicht?
 bne .loop                          ;(3 TZ) falls nicht -> .loop (daher 3 TZ)
                                    ;=====
                                    ;23 TZ  nur noch Bad-Lines!!!

Zu Anfang verbummeln wir einfach wieder etwas Zeit, dann nutzen wir das X-Register als Schleifenzähler, um unsere Rasterzeilen abzuklappern. Wir holen ab .loop jedes Mal die Werte für $d018 und $d011 aus den zugehörigen Tabellen und füllen die Register. Dann wird der Schleifenzähler erhöht und sobald wir Zeile 199 erreicht haben, verlassen wir die Schleife. Wichtig ist, dass die Schleife exakt soviele Taktzyklen benötigt, wie wir in einer Bad Line zur Verfügung haben, nämlich 23.

Ein Start zeigt euch unser erstes FLI-Bild. Die gelbe Linie über dem Ausgabebereich dient nur zur Veranschaulichung, hier beginnen wir damit Zeit zu verbummbeln.

Aber irgendwie passt etwas nicht…

Ooops, was ist denn nun los?
Ooops, was ist denn nun los?

Unsere Anzeige ist vollkommen aus dem Tritt!?

Dies liegt daran, dass wir $d011 ändern, aber nicht wieder zurückstellen!
Wir sollten $d011 also noch in unserem Raster-IRQ zurücksetzen.
Sucht das Label DoubleIRQ. Kurz dahinter findet ihr vier nop-Anweisungen. Kommentiert drei davon aus und setzt das Register 17 $d011 zurück (s. gelb markierte Zeilen):

DoubleIRQ
;*** Wir sind nun in Rasterzeile RASTER+2 und
;*** haben bisher genau 38 oder 39 Taktzyklen benötigt!!
;*** Wir können so sicher sein, da der IRQ während der NOPs auftrat.

;*** Jetzt exakt soviele Taktzyklen 'verschwenden', wie in 
;*** dieser Zeile noch zu verarbeiten sind (also 24 oder 25).
 ldx #$00                           ;(2 TZ) Platzhalter für 1. Stackpointer
 txs                                ;(2 TZ) Stackpointer vom 1. IRQ wiederherstellen
 nop                                ;(2 TZ)
 ;nop                                ;(2 TZ)
 ;nop                                ;(2 TZ)
 ;nop                                ;(2 TZ)
 lda #%00111000                     ;(2 TZ) $d011 wieder
 sta $d011                          ;(4 TZ) zurücksetzen
 bit $01                            ;(3 TZ)
 ldx $d012                          ;(4 TZ)
 lda #$01                           ;(2 TZ) weiß schonmal in den Akku
 cpx $d012                          ;(4 TZ) sind wir noch in Rasterzeile 22?
                                    ;------
                                    ;25 TZ = 63 oder 64 TZ!!!

 beq MyIRQMain                      ;(3 TZ) wenn JA einen letzten Takt 'verschwenden'
                                    ;(2 TZ) sonst einfach weiterlaufen...

Jetzt sieht es schon besser aus.

Unser erstes FLI-Bild.
Unser erstes FLI-Bild.

Wie so oft 😉 , gibt es aber noch ein weiters Problem!

Schaut euch mal den Beginn des Musters an…

Da fehlt etwas!
Da fehlt etwas!

Es fehlen drei Zeilen! Das Muster sollte mit schwarz, weiß und rot beginnen. Wir müssen hier daran denken, dass wir die Anzeige scrollen, um Bad-Lines zu erzeugen. Dadurch wandert ein Teil der Anzeige unter den Rahmen. Also öffnen wir den Rahmen doch einfach. Wie es der Zufall will, sind wir nach unserer Schleife von eben, direkt an der richtigen Stelle dafür. Ihr braucht also hinter ;23 TZ nur noch Bad-Lines!!! nur die beiden folgenden Zeilen einfügen.

 lda #%01000110                     ;Rahmen öffnen
 sta $d011

Jetzt könnt ihr das Programm starten und wir sind fast am Ziel.
Wer genau hinschaut sieht, dass die erste Zeile gelb und nicht schwarz ist. Dies liegt daran, dass wir $d018 ebenfalls noch initialisieren müssen.
Fügt direkt hinter den Zeilen von eben folgendes ein:

 lda d018Values                     ;Für den nächsten Durchlauf wieder mit der
 sta $d018                          ;richtigen Page starten

So, jetzt ist es endlich vollbracht!

Endlich ist es vollbracht, das FLI-Bild wird korrekt angezeigt!
Endlich ist es vollbracht, das FLI-Bild wird korrekt angezeigt!

Schaut ihr euch das Muster in der Vergrößerung an, dann seht ihr, dass wir unser ganz oben ausgegebenes Ziel erreicht haben.

So bunt soll das Muster werden.
Lauter Farben…

Hier noch das komplette Listing zur Kontrolle…

RASTER          = 45                ;hier den 1. Raster-IRQ auslösen
SCREENRAM       = $4000             ;BITMAP-Farben (wenn 'Pixel' %01 oder %10) 
BITMAPADDR      = $6000             ;Start der Bitmap
COLORRAM        = $d800             ;Beginn des Farb-RAMs


;*** Startadresse 
*=$0801
;** BASIC-Zeile: 2018 SYS 2062
 !word main-2, 2018 
 !byte $9e
 !text " 2062"
 !byte $00,$00,$00

!zone main
main
;*** Speicheraufteilung
 lda #%00000011                     ;Datenrichtung für Bit 0 & 1 des Port-A
 sta $dd02                          ;zum Schreiben freigeben
 lda #%00000010                     ;Bank-1
 sta $dd00                          ;auswählen
 lda $d018                          ;VIC-II Register 24 in den Akku holen
 and #%00001111                     ;das SCREENRAM in den ersten 1KB Block!
 ora #%00001000                     ;mit Bit-3 den Beginn des Bitmapspeichers
 sta $d018                          ;(hier den zweiten 8KB-Block) festlegen 

;*** MultiColor-Bitmap-Modus
 lda $d011                          ;VIC-II Register 17 in den Akku
 ora #%00100000                     ;Bitmap-Modus über Bit-5
 sta $d011                          ;einschalten
 lda $d016                          ;VIC-II Register 22 in den Akku
 ora #%00010000                     ;Multi-Color über Bit-4
 sta $d016                          ;aktivieren

;*** Rasterzeileninterrupt einrichten
 sei                                ;IRQs sperren
 lda #MyIRQ
 sta $0315
 lda #%00000001                     ;Raster-IRQs vom VIC-II aktivieren
 sta $d01a
 lda #RASTER                        ;Hier soll unsere Linie erscheinen
 sta $d012                      
 lda $d011                          ;Zur Sicherheit höchste Bit
 and #%01111111                     ;für die Rasterzeile löschen
 sta $d011
 lda #%01111111                     ;Timer-IRQs abschalten
 sta $dc0d
 lda $dc0d
 lda #%0000001                      ;evtl. aktiven Raster-IRQ bestätigen
 sta $d019
 cli                                ;Interrupts erlauben

 lda #6                             ;dunkelblau
 sta $d020                          ;als Rahmenfarbe
 lda #0                             ;schwarz 
 sta $d021                          ;für den Hintergrund
 sta $d015                          ;zur Sicherheit alle Sprites deaktivieren

 jsr clearALL                       ;die komplette 16KB-VIC-II-Bank löschen
 jsr initColors                     ;die Farben setzen
 jsr drawPattern                    ;unser Muster zeichnen 
  
 jmp *                              ;Endlosschleife

!zone clearAll
clearALL
 ldx #64                            ;Seitenzähler | 64 * 256 Bytes = 16KB
 ldy #0                             ;Byte-Zähler 
 tya                                ;0 fürs Löschen in den Akku
.loop                               ;die 16KB-Bank beginnt mit dem BS-Speicher 
 sta SCREENRAM,Y                    ;löschen
 dey                                ;Byte-Zähler verringern
 bne .loop                          ;solange nicht 0 nochmal -> .loop
 inc .loop+2                        ;MSB von 'sta SCREENRAM,Y' erhöhen
 dex                                ;Seitenzähler verringern
 bne .loop                          ;solange nicht 0 nochmal -> .loop
.loop1                          
 sta COLORRAM,Y                     ;jetzt noch das COLORRAM löschen
 sta COLORRAM+256,Y
 sta COLORRAM+512,Y
 sta COLORRAM+744,Y
 dey                                ;Byte-Zähler verringern
 bne .loop1                         ;solange nicht 0 nochmal -> .loop1
 rts                                ;zurück zum Aufrufer

!zone drawPattern
drawPattern
 ldx #7                             ;Schleifenzähler | 8 Bytes je 'Zeichen'
.loop
 lda #%01010101                     ;Muster für die erste Zeile
 sta BITMAPADDR+80,X                ;4 'Zeichen'
 sta BITMAPADDR+88,X
 sta BITMAPADDR+96,X
 sta BITMAPADDR+104,X
 lda #%10101010                     ;Muster für die zweite Zeile
 sta BITMAPADDR+400,X               ;4 'Zeichen'
 sta BITMAPADDR+408,X
 sta BITMAPADDR+416,X
 sta BITMAPADDR+424,X
 lda #%11111111                     ;Muster für die dritte Zeile
 sta BITMAPADDR+720,X               ;4 'Zeichen'
 sta BITMAPADDR+728,X
 sta BITMAPADDR+736,X
 sta BITMAPADDR+744,X
 lda #%00011011                     ;Muster für die vierte und letzte Zeile
 sta BITMAPADDR+1040,X              ;4 'Zeichen'
 sta BITMAPADDR+1048,X
 sta BITMAPADDR+1056,X
 sta BITMAPADDR+1064,X
 dex                                ;Schleifenzähler verringern
 bpl .loop                          ;solange positiv nochmal -> .loop
 rts                                ;zurück zum Aufrufer

!zone initColors
initColors
 ldy #0                             ;Schleifenzähler für die acht Farbbereiche
.loop
 ldx #3                             ;Schleifenzähler Farben für 4 'Zeichen'
 lda testColors,Y                   ;Farbe holen
.loop1
 sta SCREENRAM+10,X                 ;in den BS-Speicher
 sta SCREENRAM+50,X
 sta SCREENRAM+90,X
 sta SCREENRAM+130,X
 dex                                ;Schleifenzähler (Farben) verringern
 bpl .loop1                         ;solange positiv nochmal -> .loop1
 inc .loop1+2                       ;MSB für die zusätzlichen Bereiche erhöhen
 inc .loop1+2                       ;MSB für die zusätzlichen Bereiche erhöhen
 inc .loop1+2                       ;MSB für die zusätzlichen Bereiche erhöhen
 inc .loop1+2                       ;MSB für die zusätzlichen Bereiche erhöhen
 inc .loop1+5
 inc .loop1+5
 inc .loop1+5
 inc .loop1+5
 inc .loop1+8
 inc .loop1+8
 inc .loop1+8
 inc .loop1+8
 inc .loop1+11
 inc .loop1+11
 inc .loop1+11
 inc .loop1+11
 iny                                ;Schleifenzähler (Farbbereiche) verringern
 cpy #8
 bne .loop                          ;solange positiv nochmal -> .loop
 ldx #3                             ;Schleifenzähler Farben für 4 'Zeichen'
 lda #7                             ;Gelb
.loop2
 sta COLORRAM+10,X                  ;ins Farb-RAM
 sta COLORRAM+50,X
 sta COLORRAM+90,X
 sta COLORRAM+130,X
 dex                                ;Schleifenzähler verringern
 bpl .loop2                         ;solange positiv nochmal -> .loop2
 rts                                ;zurück zum Aufrufer

!zone MyIRQ 
;*** Raster-IRQ an Pagegrenze ausrichten, damit die Sprünge passen
!align 255,0

MyIRQ 
;*** Wenn wir hier landen, sind bereits 38-45 Taktzyklen
;*** in der aktuellen Rasterzeile (RASTER) vergangen!

;*** zweiten IRQ einrichten
;*** da die Zeit bei aktivierten ROM nicht reicht,
;*** können wir den 2. Raster-IRQ erst in der übernächsten Zeile bekommen
 lda #DoubleIRQ                    ;(2 TZ)
 sta $0315                          ;(4 TZ)
 tsx                                ;(2 TZ) Stackpointer im X-Reg. retten
 stx DoubleIRQ+1                    ;(4 TZ) und fürs zurückholen sichern!
 nop                                ;(2 TZ)
 nop                                ;(2 TZ)
 nop                                ;(2 TZ)
 lda #%00000001                     ;(2 TZ) 1. Raster-IRQ später bestätigen
                                    ;------
                                    ;26 TZ

;*** Jetzt sind 64-71 Taktzyklen vergangen und wir sind
;*** aufjedenfall in nächsten Rasterzeile (RASTER+1)!
;*** Verbraucht wurden 1-8 TZ
 inc $d012                          ;(6 TZ) 2. IRQ in der übernächsten Zeile (RASTER+2)!!!
                                    ;       $D012 wurde bereits automatisch erhöht
 sta $d019                          ;(4 TZ) IRQ bestätigen
 cli                                ;(2 TZ) Interrupts für den 2. Raster-IRQ
                                    ;       wieder freigeben

;*** Wir befinden uns in Rasterzeile RASTER+1 und 
;*** haben bisher 13-20 Zyklen verbraucht

;*** etwas Zeit verschwenden...
 ldx #$08                           ;            2 TZ
.loop 
 dex                                ;8 * 2 TZ = 16 TZ
 bne .loop                          ;7 * 3 TZ = 21 TZ
                                    ;1 * 2 TZ =  2 TZ
                                    ;          ------
                                    ;           41 TZ

;*** Bis hier sind 54-61 Taktzyklen vergannen, jetzt auf den IRQ warten...
;*** Der nächste Rasterinterrupt wird während dieser NOPs auftreten!
 nop                                ;2 TZ (55)
 nop                                ;2 TZ (57)
 nop                                ;2 TZ (59)
 nop                                ;2 TZ (51)
 nop                                ;2 TZ (63)
 nop                                ;2 TZ (65)

DoubleIRQ
;*** Wir sind nun in Rasterzeile RASTER+2 und
;*** haben bisher genau 38 oder 39 Taktzyklen benötigt!!
;*** Wir können so sicher sein, da der IRQ während der NOPs auftrat.

;*** Jetzt exakt soviele Taktzyklen 'verschwenden', wie in 
;*** dieser Zeile noch zu verarbeiten sind (also 24 oder 25).
 ldx #$00                           ;(2 TZ) Platzhalter für 1. Stackpointer
 txs                                ;(2 TZ) Stackpointer vom 1. IRQ wiederherstellen
 nop                                ;(2 TZ)
 lda #%00111000                     ;(2 TZ) $d011 wieder
 sta $d011                          ;(4 TZ) zurücksetzen
 bit $01                            ;(3 TZ)
 ldx $d012                          ;(4 TZ)
 lda #$01                           ;(2 TZ) weiß schonmal in den Akku
 cpx $d012                          ;(4 TZ) sind wir noch in Rasterzeile 22?
                                    ;------
                                    ;25 TZ = 63 oder 64 TZ!!!

 beq MyIRQMain                      ;(3 TZ) wenn JA einen letzten Takt 'verschwenden'
                                    ;(2 TZ) sonst einfach weiterlaufen...
!zone MyIRQMain
;*** Wir beginnen also immer exakt nach 3 TZ in der dritten Rasterzeile (RASTER+3)
;*** nach dem 1. Raster-IRQ (den hatten wir ja in für Zeile RASTER festgelegt)
MyIRQMain
;*** etwas Zeit vertrödeln...
 inc $d020                          ;(6 TZ) hier zur Kontrolle mal die Rahmenfarbe ändern
 dec $d020                          ;(6 TZ)
 bit $01                            ;(3 TZ)
 bit $01                            ;(3 TZ)
 ldx #0                             ;(2 TZ) Schleifenzähler für die Rasterzeilen

;*** der nächste Befehl wird direkt zu Beginn der 1. sichtbaren Zeile aufgerufen
.loop
 lda d018Values+1,X                 ;(4 TZ) Wert für $d018 aus der Tabelle holen
 sta $d018                          ;(4 TZ) und speichern
 lda d011Values+1,X                 ;(4 TZ) Wert für $d011 aus der Tabelle holen
 sta $d011                          ;(4 TZ) und speichern
 inx                                ;(2 TZ) Schleifenzähler erhöhen
 cpx #199                           ;(2 TZ) Wurde Zeile 199 erreicht?
 bne .loop                          ;(3 TZ) falls nicht -> .loop (daher 3 TZ)
                                    ;=====
                                    ;23 TZ  nur noch Bad-Lines!!!

 lda #%01000110                     ;Rahmen öffnen
 sta $d011

 lda d018Values                     ;Für den nächsten Durchlauf wieder mit der
 sta $d018                          ;richtigen Page starten
 
;*** Aufräumarbeiten
 lda #MyIRQ
 sta $0315

 lda #RASTER                        ;Original Rasterzeile setzen
 sta $d012

 lda #%00000001                     ;IRQ bestätigen
 sta $d019

 jmp $ea81                          ;zum Ende des 'Timer-Interrupts' springen
 
testColors
 !byte %00001000, %00011001, %00101010, %00111011
 !byte %01001100, %01011101, %01101110, %01111111
 
!align 255,0
d018Values
 !for x = 0 to 31
     !for y = 0 to 7
         !byte %10000*y+%1000
     !end
 !end
     
d011Values
 !for x = 0 to 31
     !for y = 0 to 7
         !byte %00111000 + y
     !end
 !end

So schön es auch ist, den FLI-Modus nun endlich ergründet zu haben, so langweilig ist unser Beispiel. Also zum Schluß noch ein Ausflug in die Praxis, wo wir übrigens auf ein weiteres Problem stoßen werden…

 

Wie sieht es in der Praxis aus?

 

Um mal schnell an ein Bild im FLI-Format zu kommen, habe ich eins konvertiert. Schauen wir uns erst mal die Unterschiede zu einer normalen Multi-Color-Bitmap an.

 

Nehmen wir dieses Bild als Ausgangspunkt…

Dieses Bild soll es sein.

…und konvertieren es, dann erhalten wir als Multi-Color-Bitmap folgendes Ergebnis…

als MC-Bitmap
Multi-Color-Bitmap

…als FLI sieht das Ganze gleich viel besser aus…

Die FLI-Version
FLI-Version

Das Bild musste etwas beschnitten werden, damit es Verzerrungsfrei in 320*200 Pixel passt.

 

Um den Unterschied noch etwas besser zu erkennen, hier beide Bilder im direkten Vergleich.

FLI und MC-Bitmap
FLI und MC-Bitmap

Wie ihr seht, bietet das FLI-Bild mehr Details, dies wird durch die zusätzlichen Farben ermöglicht.

 

Diese Bilder sind übrigens direkt das Ergebnis der Konvertierung, ohne jegliche Nachbearbeitung. In der Praxis würdet ihr natürlich noch Hand anlegen und das Bild weiter optimieren.

 

Laßt uns nun das konvertierte FLI-Bild mit unserem Programm von eben anzeigen. Dazu brauchen wir nur wenige Änderungen. Ich beschreibe hier kurz die Schritte, gleich folgt dann das komplette Listing.

 

Zunächst kann alles weg, was mit dem Muster zusammenhängt. Wir benötigen nur das Aufbauen der Tabellen! Dann binden wir am Ende des Sourcecodes einfach unser Yoda-Bild ein. Das Bild ist im FLI-Graph 2.2 (dies ist ein C64-Programm, mit dem ihr FLI-Grafiken erstellen könnt) Format gespeichert, daher binden wir das Bild ab $3b00 ins Programm ein. Dies hat den Vorteil, dass wir nur noch das Farb-RAM kopieren müssen (siehe copyToColorRAM im folgenden Listing) und schon erscheint Meister Yoda auf dem Bildschirm.

 

RASTER          = 45                ;hier den 1. Raster-IRQ auslösen
SCREENRAM       = $4000             ;BITMAP-Farben (wenn 'Pixel' %01 oder %10) 
BITMAPADDR      = $6000             ;Start der Bitmap
COLORRAM        = $d800             ;Beginn des Farb-RAMs


;*** Startadresse 
*=$0801
;** BASIC-Zeile: 2018 SYS 2062
 !word main-2, 2018 
 !byte $9e
 !text " 2062"
 !byte $00,$00,$00

!zone main
main
;*** Speicheraufteilung
 lda #%00000011                     ;Datenrichtung für Bit 0 & 1 des Port-A
 sta $dd02                          ;zum Schreiben freigeben
 lda #%00000010                     ;Bank-1
 sta $dd00                          ;auswählen
 lda $d018                          ;VIC-II Register 24 in den Akku holen
 and #%00001111                     ;das SCREENRAM in den ersten 1KB Block!
 ora #%00001000                     ;mit Bit-3 den Beginn des Bitmapspeichers
 sta $d018                          ;(hier den zweiten 8KB-Block) festlegen 

;*** MultiColor-Bitmap-Modus
 lda $d011                          ;VIC-II Register 17 in den Akku
 ora #%00100000                     ;Bitmap-Modus über Bit-5
 sta $d011                          ;einschalten
 lda $d016                          ;VIC-II Register 22 in den Akku
 ora #%00010000                     ;Multi-Color über Bit-4
 sta $d016                          ;aktivieren

;*** Rasterzeileninterrupt einrichten
 sei                                ;IRQs sperren
 lda #<MyIRQ                        ;Adresse unserer Routine in
 sta $0314                          ;den RAM-Vektor
 lda #>MyIRQ
 sta $0315
 lda #%00000001                     ;Raster-IRQs vom VIC-II aktivieren
 sta $d01a
 lda #RASTER                        ;Hier soll unsere Linie erscheinen
 sta $d012                      
 lda $d011                          ;Zur Sicherheit höchste Bit
 and #%01111111                     ;für die Rasterzeile löschen
 sta $d011
 lda #%01111111                     ;Timer-IRQs abschalten
 sta $dc0d
 lda $dc0d
 lda #%0000001                      ;evtl. aktiven Raster-IRQ bestätigen
 sta $d019

 jsr copyToColorRAM                 ;Daten ins Farb-RAM kopieren
 
 lda #0                             ;dunkelblau
 sta $D021                          ;für den Hintergrund
 lda #0                             ;schwarz 
 sta $D020                          ;als Rahmenfarbe
 sta $D015                          ;zur Sicherheit alle Sprites deaktivieren
 
 lda #$ff                           ;Muster für das Phantom-Byte
 sta BITMAPADDR+$1fff               ;setzen

 cli                                ;Interrupts erlauben
 
 jmp *                              ;Endlosschleife



!zone copyToColorRAM
copyToColorRAM
 ldx #0                         ;Schleifenzähler | 256 BYTEs
.loop
 lda flifile+$100,X             ;Farb-RAM Wert holen
 sta COLORRAM,X                 ;und speichern
 lda flifile+$200,X             ;für die nächsten 3 Pages wiederholen
 sta COLORRAM+$100,X
 lda flifile+$300,X
 sta COLORRAM+$200,X
 lda flifile+$400,X
 sta COLORRAM+$300,X
 dex                            ;Schleifenzähler verringern
 bne .loop                      ;falls NICHT 0 -> .loop
 rts                            ;sonst zurück zum Aufrufer



!zone MyIRQ 
;*** Raster-IRQ an Pagegrenze ausrichten, damit die Sprünge passen
!align 255,0

MyIRQ 
;*** Wenn wir hier landen, sind bereits 38-45 Taktzyklen
;*** in der aktuellen Rasterzeile (RASTER) vergangen!

;*** zweiten IRQ einrichten
;*** da die Zeit bei aktivierten ROM nicht reicht,
;*** können wir den 2. Raster-IRQ erst in der übernächsten Zeile bekommen
 lda #<DoubleIRQ                    ;(2 TZ) 2. Raster-IRQ einrichten
 sta $0314                          ;(4 TZ)
 lda #>DoubleIRQ                    ;(2 TZ)
 sta $0315                          ;(4 TZ)
 tsx                                ;(2 TZ) Stackpointer im X-Reg. retten
 stx DoubleIRQ+1                    ;(4 TZ) und fürs zurückholen sichern!
 nop                                ;(2 TZ)
 nop                                ;(2 TZ)
 nop                                ;(2 TZ)
 lda #%00000001                     ;(2 TZ) 1. Raster-IRQ später bestätigen
                                    ;------
                                    ;26 TZ

;*** Jetzt sind 64-71 Taktzyklen vergangen und wir sind
;*** aufjedenfall in nächsten Rasterzeile (RASTER+1)!
;*** Verbraucht wurden 1-8 TZ
 inc $d012                          ;(6 TZ) 2. IRQ in der übernächsten Zeile (RASTER+2)!!!
                                    ;       $D012 wurde bereits automatisch erhöht
 sta $d019                          ;(4 TZ) IRQ bestätigen
 cli                                ;(2 TZ) Interrupts für den 2. Raster-IRQ
                                    ;       wieder freigeben

;*** Wir befinden uns in Rasterzeile RASTER+1 und 
;*** haben bisher 13-20 Zyklen verbraucht

;*** etwas Zeit verschwenden...
 ldx #$08                           ;            2 TZ
.loop 
 dex                                ;8 * 2 TZ = 16 TZ
 bne .loop                          ;7 * 3 TZ = 21 TZ
                                    ;1 * 2 TZ =  2 TZ
                                    ;          ------
                                    ;           41 TZ

;*** Bis hier sind 54-61 Taktzyklen vergannen, jetzt auf den IRQ warten...
;*** Der nächste Rasterinterrupt wird während dieser NOPs auftreten!
 nop                                ;2 TZ (55)
 nop                                ;2 TZ (57)
 nop                                ;2 TZ (59)
 nop                                ;2 TZ (51)
 nop                                ;2 TZ (63)
 nop                                ;2 TZ (65)

DoubleIRQ
;*** Wir sind nun in Rasterzeile RASTER+2 und
;*** haben bisher genau 38 oder 39 Taktzyklen benötigt!!
;*** Wir können so sicher sein, da der IRQ während der NOPs auftrat.

;*** Jetzt exakt soviele Taktzyklen 'verschwenden', wie in 
;*** dieser Zeile noch zu verarbeiten sind (also 24 oder 25).
 ldx #$00                           ;(2 TZ) Platzhalter für 1. Stackpointer
 txs                                ;(2 TZ) Stackpointer vom 1. IRQ wiederherstellen
 nop                                ;(2 TZ)
 ;nop                                ;(2 TZ)
 ;nop                                ;(2 TZ)
 ;nop                                ;(2 TZ)
 lda #%00111000                     ;(2 TZ) $d011 wieder
 sta $d011                          ;(4 TZ) zurücksetzen
 bit $01                            ;(3 TZ)
 ldx $d012                          ;(4 TZ)
 lda #$01                           ;(2 TZ) weiß schonmal in den Akku
 cpx $d012                          ;(4 TZ) sind wir noch in Rasterzeile 22?
                                    ;------
                                    ;25 TZ = 63 oder 64 TZ!!!

 beq MyIRQMain                      ;(3 TZ) wenn JA einen letzten Takt 'verschwenden'
                                    ;(2 TZ) sonst einfach weiterlaufen...

!zone MyIRQMain
;*** Wir beginnen also immer exakt nach 3 TZ in der dritten Rasterzeile (RASTER+3)
;*** nach dem 1. Raster-IRQ (den hatten wir ja in für Zeile RASTER festgelegt)
MyIRQMain
;*** ein paar TZ vertrödeln, da wir durchs Scrolling bereits in einer Bad-Line sind!
                                    ;(3 TZ) Hier beginnter der stabile Raster-IRQ
 ldx #0                             ;(2 TZ) Schleifenzähler initialisieren
 bit $01                            ;(3 TZ)
 bit $01                            ;(3 TZ)
 bit $01                            ;(3 TZ)
 nop                                ;(2 TZ)
 nop                                ;(2 TZ)
 
;*** der nächste Befehl wird direkt zu Beginn der 1. sichtbaren Zeile aufgerufen
.loop
 lda d018Values+1,X                 ;(4 TZ) Wert für $d018 aus der Tabelle holen
 sta $d018                          ;(4 TZ) und speichern
 lda d011Values+1,X                 ;(4 TZ) Wert für $d011 aus der Tabelle holen
 sta $d011                          ;(4 TZ) und speichern
 inx                                ;(2 TZ) Schleifenzähler erhöhen
 cpx #199                           ;(2 TZ) Wurde Zeile 199 erreicht?
 bne .loop                          ;(3 TZ) falls nicht -> .loop (daher 3 TZ)
                                    ;=====
                                    ;23 TZ  nur noch Bad-Lines!!!

 lda #%01000110                     ;Rahmen öffnen
 sta $d011

;*** Aufräumarbeiten
 lda #<MyIRQ                        ;Original IRQ-Vektor setzen
 sta $0314
 lda #>MyIRQ
 sta $0315

 lda #RASTER                        ;Original Rasterzeile setzen
 sta $d012

 lda #%00000001                     ;IRQ bestätigen
 sta $d019

 jmp $ea81                          ;zum Ende des 'Timer-Interrupts' springen


!align 255,0
d018Values
 !for x = 0 to 31
     !for y = 0 to 7
         !byte %10000*y+%1000
     !end
 !end

d011Values
 !for x = 0 to 31
     !for y = 0 to 7
         !byte %111000 + y
     !end
 !end


;*** FilGraph 2.2 Dateiformat
;*** $3b00 Hintergrundfarbe je Rasterzeile
;*** $3c00 Farb-RAM
;*** $4000 Bildschirmspeicher (8 mal)
;*** $6000 die Bitmap
*=$3b00
flifile
 !bin "Yoda_1.fli",,2

Packt die Yoda-Bilder bitte direkt ins Verzeichnis mit eurem Quellcode. Die Bilder findet ihr hier im ZIP…

Startet nun das obige Programm und ihr seht unser nächstes Problem.

Der linke Rand, nicht farbig er ist!
Der linke Rand, nicht farbig er ist!

Wie ihr seht, haben wir am linken Rand falsche Farben. Dies kommt durch unser pausenloses erzeugen der Bad-Lines. Bis die greifen, dauert es einfach etwas. Um das Problem zu beheben, kann man z. B. Sprites verwenden, die diesen Bereich überdecken und ansatzweise die richtigen Farben anzeigen könnten. Dies würde aber nur nach massiven Änderungen an unserem Programm klappen. Die einfachste Lösung ist es, das Bild auf der linken Seite zu bereinigen. Wir entfernen dazu alle Pixel auf der linken Seite auf einer Breite von exakt drei Zeichen.

Bindet einfach Yoda_2.fli ein und erstellt das Programm erneut:

Hier haben wir den linken Rand bereinigt.
Hier haben wir den linken Rand bereinigt.

So sieht die Anzeige doch besser aus, oder? Es fehlen zwar links die Bildinformationen, aber es sieht einfach schöner aus.
Falls euch der linke Rand wichtiger, als der rechte ist, dann könnt ihr das Bild natürlich auch einfach mit einem Grafikprogramm etwas verschieben. Yoda_3.fli zeigt, wie das aussehen könnte. Dabei habe ich das Logo aber noch gerettet.

Das Bild wurde mit einem Grafikprogramm etwas verschoben.
Das Bild wurde mit einem Grafikprogramm etwas verschoben.

So nun seid ihr für FLI gewappnet. Da der FLI-Modus sehr viel Rechenzeit und Arbeitsspeicher verbraucht, eignet er sich in erster Linie für Demos, Titelbilder oder evtl. für sehr statische Spiele (z. B. die Umsetzung von Brett- oder Kartenspielen).

Noch einige Anmerkung

Ihr könnt den Speicherbedarf und die Rechenzeit reduzieren, wenn ihr FLI-Bilder erzeugt, die z. B. nur jede zweite oder gar vierte Zeile neue Farben benötigen.

Wenn ihr euch das FLI-Graph-Format noch mal anseht…

;*** FilGraph 2.2 Dateiformat
;*** $3b00 Hintergrundfarbe je Rasterzeile
;*** $3c00 Farb-RAM
;*** $4000 Bildschirmspeicher (8 mal)
;*** $6000 die Bitmap

…dann wundert ihr euch evtl. über den ersten Block. Ab $3b00 findet ihr für jede Rasterzeile eine eigene Hintergrundfarbe für $d021! Ihr könnt die FLI-Routine nämlich auch so bauen, dass ihr in jeder Rasterzeile eine andere Hintergrundfarbe verwendet! Dadurch habt ihr theroretisch die Möglichkeit 25 verschiedene Farben (16 über die Bildschirmspeicher + 8 über die Hintergrundfarbe + 1 aus dem Farb-RAM) je 4*8 Pixel großem Zeichen anzuzeigen. Es bleibt natürlich bei den max. 16 Farben, die der C64 beherrscht, aber durch die neuen Kombinationsmöglichkeiten lassen sich dann sehr viel schönere Grafiken erzeugen und anzeigen.

Wenn ihr euch mit dem FLI-Modus beschäftigt, läuft euch evtl. auch noch der IFLI-Modus (Interlaced Flexible Line Interpretation) über den Weg. Dabei wird der MCI-Modus (MultiColor Interlaced) mit dem eben behandelten FLI-Modus kombiniert. Dazu sollten wir uns aber erstmal im nächsten Beitrag, mit dem MCI-Modus beschäftigen.


Schrott!!Naja...Geht so...Ganz gut...SUPER! (8 Bewertungen | Ø 5,00 von 5 | 100,00%)

Loading...


ZurückWeiter

10 Gedanken zu „VIC-II: Grafikmodes – FLI“

  1. Hi Jörn, Many thanks for this awesome step by step tutorial.
    however I’m facing an issue while trying to reproduce the intermediate steps :
    I just can’t obtain the intermediate result for “Unser erste FLI-Bild.”
    I get the exact same result as what i get in next step while opening the borders. And of course i can get the final result.
    Trying to go back from the final result, I can produced the previous state (where borders have been opened). However i can get the exact same result for the previous step called “Unser erste FLI-Bild.”.
    So this is quite a bit annoying to understand the whole process.
    May you be so kind to post the full code which produces the result of “Unser erste FLI-Bild.” ?
    Thanks !
    Olivier.

    1. Hi Olivier.
      I checked the description and it was faulty.
      I’m sorry for that!
      I changed the description behind „Das erste FLI-Bild“.
      Later I will work through it again and check if everything is working as intended.

      Greetings Jörn

      1. Many Thanks Jörn !!!!
        I will review again and let you know if obviously everything is matching as expected !
        Actually since i can’t speak german, I’m spending a lot of time translating all your tutorials to english ( I’m french and i use deepL to translate your tutorials) – for private usage of course – and also doing a little bit of conversion to make the code work with Kickasm (The assembler I’m used to). I want to thank you a lot for sharing this information with so many detailed explainations ! It’s so much fun to finally understand such topics ! Thanks again !
        Olivier.

      2. Hi Jörn,
        I have just checked the modifications you have done and i’m happy because it all perfectly makes sense and work as expected 🙂
        I noticed you have modified the main full code, and i think there’s a little quirk at line 259 :

        ;*** Aufräumarbeiten
        lda #MyIRQ
        sta $0315

        Shouldn’t it be ? :
        lda #MyIRQ
        sta $0315

        Again thanks a lot !
        Olivier.

      3. Ah it looks like there’s a problem with COPY PASTE, trying again :

        At line 259 you have :

        “lda #MyIRQ”
        “sta $0315 ”

        shouldn’t it be :

        ” lda #MyIRQ”

        ” sta $0315″

  2. Hallo Jörn,
    eine Kleinigkeit stimmt im Text nicht, denn dort steht: “Für $d018 benötigen wir im oberen Nibble die Adresse des Bildschirmspeichers und im unteren müssen wir daran denken, den MultiColor-Modus zu aktivieren.”
    Im unteren Nibble von $d018 steht allerdings tatsächlich die Position der Bitmap; in diesem Fall bedeutet der Wert #%1000 im unteren Nibble, dass die Bitmap bei $6000 liegt. Mit dem MultiColor-Modus hat $d018 nichts zu tun.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Protected by WP Anti Spam