Nun wirds mir aber zu bunt… 😉
Nachdem wir im letzten Beitrag VIC-II: Grafikmodes – Bitmap (Hi-Res) die Bitmap-Grundlagen und den Hi-Res-Modus kennengelernt haben, ist es nun an der Zeit mehr Farbe ins Spiel zu bringen.
Multi-Color-Modus
Wie schon beim Textmodus und den Sprites gesehen, können wir auch bei Bitmaps, auf Kosten der Auflösung, für mehr Farbe sorgen. Wie bei den eben erwähnten Beispielen, wird auch hier die Auflösung in X-Richtung halbiert, weil dann je zwei Bits für die Pixelfarbe zuständig sind. Daher beträgt die Auflösung im Multi-Color-Modus auch nur noch 160*200 Pixel.
Als Basis für unsere nächsten Schritte, verwenden wir das abschließende Listing aus VIC-II: Grafikmodes – Bitmap (Hi-Res):
;*** VIC-II Speicher-Konstanten VICBANKNO = 0 ;Nr. (0 - 3) der 16KB Bank | Standard: 0 VICSCREENBLOCKNO = 1 ;Nr. (0 -15) des 1KB-Blocks für den Textbildschirm | Standard: 1 VICCHARSETBLOCKNO = 2 ;Nr. (0 - 7) des 2KB-Blocks für den Zeichensatz | Standard: 2 VICBITMAPBBLOCKNO = 1 ;Nr. (0 - 1) des 8KB-Blocks für die BITMAP-Grafik | Standard: 0 VICBASEADR = VICBANKNO*16384 ;Startadresse der gewählten VIC-Bank | Standard: $0000 VICCHARSETADR = VICBASEADR+VICCHARSETBLOCKNO*2048 ;Adresse des Zeichensatzes | Standard: $1000 ($d000) VICSCREENRAM = VICBASEADR+VICSCREENBLOCKNO*1024 ;Adresse des Bildschirmspeichers VICBITMAPADR = VICBASEADR+VICBITMAPBBLOCKNO * 8192 ;Adresse der BITMAP ZP_HELPADR1 = $fb ;Hilfsadresse auf der Zero-Page ;*** Startadresse *=$0801 ;** BASIC-Zeile: 2018 SYS 2062 !word main-2, 2018 !byte $9e !text " 2062" !byte $00,$00,$00 main ;*** Bitmap-Modus aktivieren lda $d011 ;VIC-II Register 17 in den Akku ora #%00100000 ;Bitmap-Modus sta $d011 ;aktivieren ;*** Start des Bitmapspeichers festlegen lda $d018 ;VIC-II Register 24 in den Akku holen and #%11110111 ;Mit Bit-3 ora #VICBITMAPBBLOCKNO*8 ;den Beginn des sta $d018 ;Bitmapspeichers festlegen jsr clrHiResBitmap clc ldx #0 ldy #0 jsr setPixel sec ldx #63 ldy #0 jsr setPixel sec ldx #63 ldy #199 jsr setPixel clc ldx #0 ldy #199 jsr setPixel clc ldx #160 ldy #100 jsr setPixel jmp * ;Endlosschleife !zone setPixel ;*** X-Position = Carry + X-Register ;*** Y-Position = Y-Register setPixel lda #<VICBITMAPADR ;Bitmap-Adresse auf die Zero-Page sta ZP_HELPADR1 lda #>VICBITMAPADR sta ZP_HELPADR1+1 bcc .skip ;falls Carry gelöscht weiter -> .skip inc ZP_HELPADR1+1 ;sonst MSB+1 clc ;und C wieder löschen (für ADC) .skip txa ;X in den Akku pha ;und für später merken and #%11111000 ;'Pixel'-Position ausblenden adc ZP_HELPADR1 ;Anzahl der Bytes für X-Position addieren sta ZP_HELPADR1 ;und speichern bcc .skip1 ;falls C gelöscht ist weiter -> .skip1 inc ZP_HELPADR1+1 ;sonst MSB wegen gesetztem Carry erhöhen .skip1 tya ;Y in den Akku pha ;und merken lsr ;jetzt durch 8 teilen lsr lsr beq .skip2 tay .loop clc ;Für jede 'Textzeile' 320 Bytes addieren lda ZP_HELPADR1 adc #64 sta ZP_HELPADR1 lda ZP_HELPADR1+1 adc #1 sta ZP_HELPADR1+1 dey bne .loop .skip2 pla and #%00000111 ;Bytes für die 'Ziel-Textzeile' tay ;ins Y-Register (für Y-nach-indizierte-Adr.) pla ;jetzt noch die BIT-Position (X) berechnen and #%00000111 tax lda #$00 sec .loop1 ror ;Zielpixel 'einrotieren' dex bpl .loop1 ;wiederhole, solange X positiv ora (ZP_HELPADR1),Y ;mit bisherigen Wert ODER-Verknüpfen sta (ZP_HELPADR1),Y ;und speichern rts ;zurück zum Aufrufer !zone clrHiResBitmap ;*** Die 8KB der Hi-Res BITMAP löschen clrHiResBitmap jsr clrHiResColor lda #<VICBITMAPADR ;auf die Zero-Page sta ZP_HELPADR1 lda #>VICBITMAPADR sta ZP_HELPADR1+1 ldx #32 ;Schleifenzähler 32 Pages (32 * 256 = 8192 = 8KB) ldy #0 ;Schleifenzähler für 256 BYTES je Page tya ;Akku auf 0 setzen .loop sta (ZP_HELPADR1),Y ;Akku 'ausgeben' dey ;Y verringern bne .loop ;solange größer 0 nochmal -> .loop inc ZP_HELPADR1+1 ;Adresse auf der Zeropage um eine Page erhöhen dex ;Pagezähler verringern bne .loop ;solange größer 0 nochmal -> .loop rts ;zurück zum Aufrufer !zone clrHiResColor ;*** Bildschirmspeicher löschen clrHiResColor ldx #0 ;Schleifenzähler 256 BYTES lda #$10 .loop sta VICSCREENRAM,X ;1. Page des Bildschirmspeichers sta VICSCREENRAM+256,X ;2. Page sta VICSCREENRAM+512,X ;3. Page sta VICSCREENRAM+768,X ;4. Page dex ;Schleifenzähler verringern bne .loop ;solange nicht 0 nochmal -> .loop rts ;zurück zum Aufrufer
Ihr erinnert euch bestimmt, dass das Programm einen weißen Pixel in jede Ecke und in die Mitte des Bildschirms setzt…
Das Erste, was wir nun machen wollen, ist natürlich das Einschalten des Multi-Color-Modus. Dies geschieht, wie schon beim Multi-Color-Text, über das Register 22 $d016 des VIC-II (vgl. VIC-II: Die Register). Wir müssen dort also nur wieder das Bit-4 setzen. Ergänzen wir das Programm (wie wäre es bei main hinter sta $d011) um drei kleine Zeilen:
lda $d016 ;VIC-II Register 22 in den Akku ora #%00010000 ;Multi-Color über Bit-4 sta $d016 ;aktivieren
Starten wir das Programm direkt einmal…
Wie ihr seht, hat sich unsere Anzeige merklich verändert. Der Hintergrund ist dunkelblau, auf der linken Seite und in der Mitte sind die Punkte schwarz (etwas schwer zu erkennen), nur ganz rechts noch weiß. Außerdem sollte euch bei genauem Betrachten auffallen, dass die Pixel (wie bereits erwähnt) nun doppelt so breit sind.
Warum ist die Anzeige, wie sie ist?
Zunächst sieht es so aus, als würde unsere Punktberechnung noch funktionieren. Immerhin erscheinen die Punkte an der richtigen Stelle. Ihr wisst aber bereits, dass jetzt zwei Pixel für einen Punkt verwendet werden. Unsere Funktion setPixel ist aber noch auf Hi-Res ausgelegt. Korrigieren wir diese für den Multi-Color-Modus. Bevor wir das machen, wollen wir aber noch eine diagonale Linie zeichnen, die uns eine genauere Kontrolle unserer Ausgabe ermöglicht. Ersetzt dazu die bisherigen setPixel-Anweisungen in main, mit den folgenden Zeilen.
.loop clc ldx test ldy test jsr setPixel inc test lda test cmp #160 bne .loop
Hier zeichnen wir einfach eine diagonale Linie, da wir das X- & Y-Register mit den selben Werten füllen und dann setPixel aufrufen. Die Linie sollte eigentlich über die gesamte Bildschirmbreite gehen, schließlich haben wir bei Multi-Color eine max. Breite von 160 Pixel. Fügt hinter jmp * noch die neue Variable test hinzu (ist ja nur zum Testen gedacht).
test !byte 0
Ein Start zeigt uns, dass noch einiges schief geht:
OK, es wurde oben bereits erwähnt, dass setPixel noch für Hi-Res ausgelegt ist. Dies müssen wir jetzt ändern. Als erstes brauchen wir das Carry-Flag nicht mehr, um die X-Position zu übergeben! Schließlich passt nun die max. X-Position von 159 in 8-Bit. Ändern wir den Beginn von setPixel und ersetzen alles bis zu .skip1 mit diesen Zeilen.
!zone setPixel ;*** X-Position = X-Register ;*** Y-Position = Y-Register setPixel lda #<VICBITMAPADR ;BITMAP-Adresse auf die Zero-Page sta ZP_HELPADR1 lda #>VICBITMAPADR sta ZP_HELPADR1+1 txa ;X in den Akku pha ;und für später merken and #%11111100 ;'Pixel'-Position ausblenden (jetzt nur 2-BITs) asl ;BYTE-Anzahl verdoppel (2-BIT = 1-Pixel!) bcc .skip ;falls Carry gelöscht -> @skip inc ZP_HELPADR1+1 ;sonst MSB erhöhen clc ;und C löschen .skip adc ZP_HELPADR1 ;Anzahl der BYTES für X-Position addieren sta ZP_HELPADR1 ;und speichern bcc .skip1 ;falls C gelöscht ist weiter -> @skip inc ZP_HELPADR1+1 ;sonst MSB wegen gesetztem Carry erhöhen .skip1
Nach dem Kopieren des X-Registers in den Akku, blenden wir beim and jetzt nur noch die beiden unteren Bits der X-Position aus. Bei Multi-Color passen in ein BYTE schließlich nur vier Pixel. Danach müssen wir den Wert im Akku mit asl verdoppeln und das Carry-Flag beachten. Sollte dies gesetzt sein, dann wird das MSB auf der Zero-Page um eins erhöht und das C-Flag wieder gelöscht. Zum Schluß addieren wir die Byte-Anzahl aus dem Akku auf das LSB. Wer mag kann das Programm kurz starten und wird sehen, dass es noch nicht wirklich passt.
Wir haben eben ja schon gesehen, dass für die exakte Pixelposition in X-Richtung nur noch die unteren zwei Bits entscheidend sind. Dies müssen wir am Ende von setPixel natürlich auch noch beachten. Sucht das Label .loop1 und ändert die gelb hervorgehobenen Zeilen.
and #%00000011 tax lda #$00 sec .loop1 ror ;jetzt zwei Zielpixel 'einrotieren' ror
Wie erwähnt verwenden wir nur noch die unteren beiden Bits für die X-Position und rotieren dann in der Schleife bei jedem Durchlauf zwei Pixel (die definieren jetzt ja einen Punkt).
Ein Start zeigt endlich die diagonale Linie…
Wir haben es uns bei setPixel natürlich sehr einfach gemacht, indem wir immer zwei Bits für einen Pixel setzen. Eigentlich sollten wir auch noch die gewünschte Farbe an die Funktion übergeben. Schauen wir daher erstmal, wo die Farben herkommen.
Dazu geben wir in main (direkt vor jmp *) einmal alle Farbkombinationen, die mit zwei Bits möglich sind, aus. Damit wir die Ausgabe besser erkennen, machen wir dies achtmal untereinander.
lda #%11100100 sta VICBITMAPADR+8 sta VICBITMAPADR+9 sta VICBITMAPADR+10 sta VICBITMAPADR+11 sta VICBITMAPADR+12 sta VICBITMAPADR+13 sta VICBITMAPADR+14 sta VICBITMAPADR+15
Woher die Farben genau kommen, zeigt uns diese Tabelle:
%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
Zur Kontrolle könnt ihr ja nochmal am Ende von main, wieder direkt vor dem jmp *, die Farben auf andere Werte setzen, z. B. mit…
lda #0 ;schwarz für den sta $D021 ;Hintergrund lda #%01001100 ;lila & grau in den sta $0401 ;Bildschirmspeicher lda #7 ;gelb ins sta $D801 ;Farb-RAM
Damit sind uns nun alle Grundlagen für den Multi-Color-Modus bekannt. Wer möchte kann zur Übung ja die Funktion setPixel mal so erweitern, dass man auch die gewünschte Farbquelle übergeben kann. Ich verzichte darauf und möchte lieber, wie versprochen, eine richtige Grafik anzeigen.
Ein Koala-Bild anzeigen
KoalaPainter war bzw. ist eines der bekanntesten Grafikprogramme für den C64. Die damit erstellten Bilder wurden im sog. Koala-Format gespeichert, das auch von anderen Programmen unterstützt wurde. Ein Beispielbild, das ihr bitte ins Verzeichnis mit eurem Quelltext entpackt, findet ihr im ZIP.
MultiColor
Wir verwenden hier das unkomprimierte Koala-Format, das sehr simpel aufgebaut ist.
Offset (hex): Inhalt 0000 - 1f3f : Bitmap 8000 Bytes 1f40 - 2327 : Bildschirmspeicher 1000 Bytes 2328 - 270f : Farb-RAM 1000 Bytes 2710 : Hintergrundfarbe 1 Byte
Wir betten die Grafik einfach direkt in unser Programm ein. Dabei legen wir sie so, dass sie direkt bei unserem Speicherbereich für die Bitmap beginnt. Dann müssen wir nur noch die Daten für den Bildschirmspeicher und das Color-RAM kopieren, sowie die Hintergrundfarbe setzen und schon sehen wir unser Bild. Ein entsprechendes Programm lässt sich schnell erstellen…
;*** VIC-II Speicher-Konstanten VICBANKNO = 0 ;Nr. (0 - 3) der 16KB Bank | Standard: 0 VICSCREENBLOCKNO = 1 ;Nr. (0 -15) des 1KB-Blocks für den Textbildschirm | Standard: 1 VICCHARSETBLOCKNO = 2 ;Nr. (0 - 7) des 2KB-Blocks für den Zeichensatz | Standard: 2 VICBITMAPBBLOCKNO = 1 ;Nr. (0 - 1) des 8KB-Blocks für die BITMAP-Grafik | Standard: 0 VICBASEADR = VICBANKNO*16384 ;Startadresse der gewählten VIC-Bank | Standard: $0000 VICCHARSETADR = VICBASEADR+VICCHARSETBLOCKNO*2048 ;Adresse des Zeichensatzes | Standard: $1000 ($d000) VICSCREENRAM = VICBASEADR+VICSCREENBLOCKNO*1024 ;Adresse des Bildschirmspeichers VICBITMAPADR = VICBASEADR+VICBITMAPBBLOCKNO*8192 ;Adresse der BITMAP VICCOLORRAM = $d800 ;*** Startadresse *=$0801 ;** BASIC-Zeile: 2018 SYS 2062 !word main-2, 2018 !byte $9e !text " 2062" !byte $00,$00,$00 main ;Bitmap-Modus aktivieren lda $d011 ;VIC-II Register 17 in den Akku ora #%00100000 ;Bitmap-Modus sta $d011 ;aktivieren lda $d016 ;VIC-II Register 22 in den Akku ora #%00010000 ;Multi-Color über Bit-4 sta $d016 ;aktivieren ;*** Start des Bitmapspeichers festlegen lda $d018 ;VIC-II Register 24 in den Akku holen and #%11110111 ;Mit Bit-3 ora #VICBITMAPBBLOCKNO*8 ;den Beginn des sta $d018 ;Bitmapspeichers festlegen jsr showKoala ;Koala-Bild anzeigen jmp * ;Endlosschleife !zone showKoala ;*** Unkomprimiertes Koalabild anzeigen ;*** Offset (hex): Inhalt ;*** 0000 - 1F3f : Bitmap 8000 Bytes ;*** 1f40 - 2327 : Bildschirmspeicher 1000 Bytes ;*** 2328 - 270f : Farb-RAM 1000 Bytes ;*** 2710 : Hintergrundfarbe 1 Byte showKoala ldx #$00 .loop lda VICBITMAPADR+$1F40,X ;Farbe für Bildschirm-Speicher lesen sta VICSCREENRAM,X ;und schreiben lda VICBITMAPADR+$2328,X ;Farbe für COLOR-RAM lesen sta VICCOLORRAM,X ;und schreiben lda VICBITMAPADR+$2040,X ;für die nächsten drei Pages wiederholen sta VICSCREENRAM+256,X lda VICBITMAPADR+$2428,X sta VICCOLORRAM+256,X lda VICBITMAPADR+$2140,X sta VICSCREENRAM+512,X lda VICBITMAPADR+$2528,X sta VICCOLORRAM+512,X lda VICBITMAPADR+$2240,X sta VICSCREENRAM+768,X lda VICBITMAPADR+$2628,X sta VICCOLORRAM+768,X dex ;Schleifenzähler verringern bne .loop ;solange ungleich 0 -> .loop lda VICBITMAPADR+$2710 ;Hintergrundfarbe holen sta $d021 ;und setzen rts ;zurück zum Aufrufer *=$2000 ;Zieladresse für das Bild !bin "Pic0.koa" ;Bild einbinden
Wie ihr seht, ist es wirklich sehr einfach, ein Bild anzuzeigen. Da die Bitmap-Daten durch *=$2000 bereits an der richtigen Position liegen, kopiert showKoala nur noch die Farbinformationen und setzt die Hintergrundfarbe.
Ein Koala-Beispiel
Damit haben wir uns die standard Grafikmöglichkeiten des C64 erarbeitet. Es gibt aber, wie bereits erwähnt, noch neue Grafikmöglichkeiten, z. B. FLI. Einige davon werden wir uns in den folgenden Beiträgen einmal anschauen.
Bei mir zeigt er Dein Raumschiff korrekt an, aber wenn ich ein anderes Koala Bild nehme (genau: exportiert mit https://mcdraw.xyz), dann muss ich bei !bin “bild.kla” einen zusätzlichen Offset von 2 Bytes angeben, also: !bin “bild.kla”,,2 – dann wird das Bitmap immerhin korrekt angezeigt, aber die Farben sind falsch. Genau: die Hintergrundfarbe, die wird scheinbar nicht gesetzt. Ich habe noch nicht herausgefunden, was da das Problem ist…
Hallo Patrick,
das von dir verlinkte Tool packt die Ladeadresse an den Anfang der Datei, daher musst du die, wie von dir erwähnt, überspringen.
Hast du beim „Laden“ der Hintergrundfarbe evtl. das Dollarzeichen vergessen (lda VICBITMAPADR+$2710)?
Gruß, Jörn
Hi,
kurze Randnotiz. Mir wird gesagt “Redefinition of Label .loop”, wenn ich die setPixel-Anweisungen erstze (wir haben schon einen “.loop” in “!zone clrHiResColor”. Mache ich was falsch, oder muss der loop wirklich umbenannt werden?
LG
Jan
Hallo Jan,
du machst nichts falsch.
Wie ich eben feststellen musste, liegt es am aktuellen C64 Studio.
Seit dem Übergang von Version 5.8 auf 5.9 kommt es zum von dir erwähnten Fehler, wenn man das Beispiel erstellt.
Füge vor dem Label main, erstmal die Zeile „!zone main“ ein, dann scheint es zu klappen.
Ich werde das Verhalten erstmal genauer untersuchen.
Gruß,
Jörn
Zur Info:
Das Problem war ein Fehler im C64 Studio.
Dieser trat ab der Version 5.9 auf und wurde eben korrigiert: C64 Studio Version 6.2.4