Erstellt: 29. März 2014 (zuletzt geändert: 1. November 2021)

VIC-II: Grafikmodes – BITMAP (Multi-Color)

Nun wirds mir aber zu bunt… 😉

C64 Studio & AMCE

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…

Einzelne Punkte mit SETPIXEL setzen.
Einzelne Punkte mit SETPIXEL setzen.

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…

Ausgabe bei aktiviertem Multi-Color.
Ausgabe bei aktiviertem Multi-Color.

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:

Die Linie sieht nicht wie geplant aus!
Die Linie sieht nicht wie geplant aus!

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 nähern uns der Lösung.
Wir nähern uns der Lösung.

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…

Da ist die Linie.
Da ist die 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.

Die 16 Farben des C64.
Die 16 Farben des C64.

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

BITMAP_MultiColor_05 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
Geänderte Farben
Geänderte Farben

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.

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.

Hier ist unser Koala-Bild.
Hier ist unser Koala-Bild.

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.


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

Loading...


ZurückWeiter

5 Gedanken zu „VIC-II: Grafikmodes – BITMAP (Multi-Color)“

  1. 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…

    1. 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

  2. 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

    1. 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

Schreibe einen Kommentar

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

Protected by WP Anti Spam