Einsteigertutorial 2: Einführung in die Screenmodes:

[Einleitung] [... noch ein Wort zum Compiler] [Mode 3] [Mode 4] [Mode 5] [Ein kleiner Bildbetrachter] [... und ich kann doch zählen] [Die Programme downloaden (Source + Rom)]

Einleitung

Nach dem Ihr im ersten Teil des Tutorials schon mal ein kleines GBA Programm geschrieben und getestet habt, versuchen wir euch im zweiten Teil die Grafikschnittstelle des GBA ein wenig näher zu bringen. Wir werden versuchen die Grundbegriffe der Grafik zu erläutern und einen kleinen Bildbetrachter für den GBA zu programmieren. Solltet Ihr irgendwann im Text mal den Faden verlieren, so braucht Ihr keine Angst zu haben. Es sieht oft komplizierter aus als es ist und das Verständnis kommt spätestens nach dem Ihr die Texte ein zweites mal gelesen habt. Bloß nicht aufgeben. Aller Anfang ist bekanntlich schwer. Vorneweg möchte ich noch bemerken, das viele Anregungen und Sourcen, die ich verwendet habe von Dovoto und seinem „Pern Project“ stammen. Besucht ihn doch mal unter: http://emuholic.emulous.com/~dovoto/

... noch ein Wort zum Compiler

Wer noch nie mit dem Microsoft Visual Studio gearbeitet hat sollte diesen Abschnitt überspringen. Im ersten Teil wurde die Installation des Compilers schon ausführlich besprochen. Hier noch ein kleiner Tip: Wer so wie ich das arbeiten mit dem Microsoft Visual Studio gewöhnt ist, braucht sich davon nicht zu trennen. Es gibt einen GBA Project Wizard, mit dem Ihr alle Programme im Visual Studio programmieren und kompilieren könnt: http://emuholic.emulous.com/~dovoto/GBA_AW_r2-6.zip Einfach die Datei herunterladen und in das Template Verzeichnis des Visual Studios entpacken (meist „C:\Programme\Microsoft Visual Studio\Common\MSDev98\Template“). Wenn Ihr nun das Visual Studio startet habt Ihr bei den Projekten eine neue Auswahl „GBA Project“. Beim Start des Wizards müsst Ihr erst das Verzeichnis des GBA Development Kits angeben ( „C:\Devkitadv\“) und dann die Bibliotheken die Ihr verwendet wählen. Wählt am besten die des „PERN Projects“ bei CRT und HEADERS. Achtung bei der Wahl des Projektverzeichnisses. Es dürfen keine Leerzeichen im Verzeichnisnamen vorkommen. Nach der Installation und dem anlegen des Projektes habt Ihr sämtliche Vorzüge des Visual Studios zur Verfügung. Zu beachten ist hier allerdings, das die fertige Datei nicht im DEBUG Verzeichnis erscheint sondern im Projektverzeichnis. Außerdem gibt der Compiler nur die Zeilennummern mit Fehlern aus. Es ist nicht möglich einfach durch Klicken auf den Fehler an die entsprechende Zeile im Sourcecode zu springen.

Mode 3

Dieser Mode ist wohl am einfachsten zu verstehen. Wir haben Ihn ja auch schon in unserem ersten Programm verwendet. Der Bildschirm besteht hier einfach aus einem 240*160 = 38400 Pixel großem Array. Dieses Array ist eindimensional, das heißt wir können nicht einfach die x und y Koordinaten eines Pixels angeben, sondern müssen uns das Bild so vorstellen, dass alle Zeilen hintereinander stehen. Der Index des 4. Pixels in der 5. Zeile errechnet sich also wie folgt: (4 – 1) + ( (5 – 1) * 240 ) = 963. Um also genau an diesem Pixel einen Punkt zu setzen wäre folgendes Programm notwendig:
0 | #include "gba.h"
1 | #include "screenmode.h"
2 |
3 | int main(void)
4 | {
5 | SetMode(MODE_3 | BG2_ENABLE);
6 | VideoBuffer[963] = (31 << 10);
7 | while(1);
8 | }
Wem die Funktion der einzelnen Befehle noch nicht klar ist, der sei an den ersten Teil des Tutorials verwiesen. In diesem Modus besteht jedes Pixel aus 16Bit. Das heißt wir müssen die unterschiedliche Farben aus den Anteilen Rot, Grün und Blau zusammensetzen. Um nicht ständig aufwendig umrechnen zu müssen hat Dovoto ein kleines Makro geschrieben, das aus den unterschiedlichen Werten für Rot, Grün und Blau einen 16Bit Wert erzeugt:
0 | #define RGB(r,g,b) ((r)+((g)<<5)+((b)<<10))
Die einzelnen Farbanteile können Hier Werte zwischen 0 und 31 annehmen. Um zum Beispiel das oben gesetzte Pixel in reinem Blau anzuzeigen müssen wir die Zeile mit dem setzen des Pixels wie folgt modifizieren:
0 | VideoBuffer[963] = RGB(0 ,0 ,31);
Nun solltet Ihr in der Lage sein verschiedene Farben auf den Bildschirm zu zeichnen. Versucht doch mal folgendes Programm zu verstehen:
0 | #include "gba.h"
1 | #include "screenmode.h"
2 |
3 |
4 | #define RGB(r,g,b) ((r)+((g)<<5)+((b)<<10))
5 |
6 | unsigned short int * videobuffer = (unsigned short int *) 0x6000000;
7 |
8 | int main(void)
9 | {
10 | SetMode(MODE_3 | BG2_ENABLE);
11 | for(int i=0; i< 38400; i++)
12 | {
13 | if(((i%240) > 80) && ((i%240) <= 160) )
14 | videobuffer[i] = RGB(0,0,31);
15 | else if((i%240) > 160)
16 | videobuffer[i] = RGB(0,31,0);
17 | else
18 | videobuffer[i] = RGB(31,0,0);
19 | }
20 | while(1);
21 | }
Dieses Programm zeichnet drei verschiedene Balken auf dem Bildschirm. Das Ergebnis sollte so aussehen: Was genau macht also das Programm. Das Macro RGB habe ich oben ja schon erklärt. Die Zeile:
0 | unsigned short int * videobuffer = (unsigned short int*) 0x6000000;
sagt dem GBA an welcher Adresse sich der Videospeicher befindet. Eine Adresse ist nichts anderes als ein Wegweiser für den GBA, der ihm sagt an welcher Stelle im Speicher er etwas schreiben oder lesen soll. Mit dem Ausdruck i%240 bestimme ich den sog. Modulo zu 240. Ich teile den Wert i durch 240 und lasse mir den ganzahligen Rest ausgeben. Wenn i z.B den Wert 500 annimmt, dann ergibt i%240 den Wert 20, da die Zahl 500 den Wert 240 zwei mal enthält, aber 2*240 = 480 ist, fehlen mir genau 20 um den Wert 500 zu erreichen. Was bringt mir der Modulo an dieser Stelle? Erinnert Ihr euch, das ich auf die x und y Koordinate eines Pixels nicht direkt zugreifen kann? Um also die x-Koordinate eines Pixels zu bestimmen muss ich den Index des Bildspeichers mit der Breite einer Zeile umrechnen. Und genau das macht der Modulo. Er gibt mir den x-Wert eines Pixels, das ich nur durch den Index des Bildspeichers gegeben habe. Und mit der Abfrage, ob dieser x-Wert kleiner als 80 bzw. größer als 160 ist, kann ich den Bildschirm in drei gleich große Teile zerlegen. Soviel zum Zeichnen in Mode 3. Obwohl er der einfachste Modus ist, sollte man ihn nicht so häufig verwenden. Da für jeden Pixel ein 16Bit Wert benötigt wird brachen wir 16 * 38400 = 614400 Bit oder 75kByte Speicher um ein Bild darzustellen. Der Grafikspeicher des GBA ist jedoch nur 96kByte groß, so das wir immer nur ein Bild im Speicher haben können. Da der Modus 3 zudem auch noch recht langsam ist, ist es nicht möglich Animationen im Mode 3 abzuspielen. Er ist also nur für Standbilder geeignet.

Mode 4

Der Mode 4 ist nicht viel komplizierter zu verstehen als der Mode 3. Ähnlich wie im Mode 3 können wir die Pixel direkt einfach durch Angabe der Adresse im Speicher zeichnen. Im Unterschied zum Mode 3 geben wir hier jedoch nicht direkt die Farbe des Pixels an, sondern greifen auf eine sogenannte Palette zurück. Eine Palette ist ähnlich wie der Bildspeicher ein eindimensionales Array umfasst jedoch nur 256 Einträge. In jedem Feld dieses Arrays steht ein 16Bit Farbwert und man gibt dann beim Zeichnen nicht mehr den Farbwert selbst an, sondern nur den Index der Farbe in der Palette. Man kann sich die Palette wie einen Malkasten mit 256 verschiedenen Farben vorstellen. Um zu zeichnen braucht man hier nicht die Zusammensetzung der Farbe zu kennen sondern muss nur wissen an welcher Stelle des Malkastens die Farbe sich befindet. Um Gegensatz zum echten Leben kann man allerdings die Farben nicht mischen. Im Gegensatz zum Mode 3 hat man aber nun nicht mehr 32*32*32 = 32768 Farben zur Verfügung sondern nur noch die 256 Farben, die in der Palette gespeichert sind. Warum sollte man also diesen Modus verwenden? Ganz einfach. Dieser Modus ist viel schneller als der Mode 3 und belegt weniger Speicher. Da wir nur noch 8Bit für einen Pixel benötigen braucht ein Bild im Grafikspeicher auch nur 8*38400 = 37,5kByte. Dadurch können wir zwei komplette Bilder im Videospeicher ablegen. Man kann also ein Bild im Speicher zeichnen, während das zweite gerade angezeigt wird. Nur in diesem Modus sind Bildschirmfüllende Animationen möglich. So, nun genug von den Vorteilen. Wir wollen mal versuchen ein kleines Programm zu schreiben, das im Mode 4 arbeitet. Wir wollen nun ein Bild mit einer Palette auf dem GBA darstellen. Da das „Zeichnen“ eines Bildes nur durch Angabe der Pixelpositionen und Farben auf Dauer etwas mühsam ist, verwende ich hier ein Programm da mir ein Bild in den entsprechenden Quellcode umrechnet. Es gibt sehr viele solcher Programme und meine Wahl ist auf den GBA Gfx Converter gefallen: http://www.gbadev.org/files/AGBGFXCon.zip In diesem Programm könnt Ihr ein Bild der Größe 240*160 Pixel mit 256 Farben laden und umwandeln. Ihr bekommt dann das Bild als .C Datei. Diese Datei nennt Ihr am geschicktesten nach .h um und entfernt die zweite Zeile, in der steht: #include Standardmäßig erzeugt AGBGFXCon zwei Arrays namens Palette und Map in denen die Palette des Bildes und die Bilddaten gespeichert werden. Beim folgenden Programm habe ich mich für das Planetgameboylogo (http://www.planetgameboy.de) entschieden. Doch nun zum eigentlichen Programm:
0 | #include "gba.h"
1 | #include "screenmode.h"
2 | #include "PGLight.h"
3 |
4 | u8* videobuffer = (u8*)0x6000000;
5 | u16* palettebuffer = (u16*)0x5000000;
6 |
7 | void WAIT(int x) {for(int i=0;i
8 |
9 | int main(void)
10 | {
11 | int i, ,j=0;
12 |
13 | SetMode(MODE_4 | BG2_ENABLE);
14 |
15 | for(i=0;i<256;i++)
16 | palettebuffer[i] = PGLight_Palette[i];
17 |
18 | for(i=0;i<38400;i++)
19 | videobuffer[i] = PGLight_Map[i];
20 |
21 | while((*KEYS) & KEY_A);
22 |
23 | while(1)
24 | {
25 | for(i=0;i<256;i++)
26 | if((i+j) < 255)
27 | palettebuffer[i] = PGLight_Palette[i+j];
28 | j++;
29 |
30 | if(j==256)
31 | j=0;
32 |
33 | WAIT(3000);
34 | }
35 | }
Dieses Programm sollte folgendes auf dem Bildschirm darstellen: Sobald man jedoch die Taste A auf dem GBA drückt, sollte das ganze Bild wie wild anfangen zu flimmern. Wie genau funktioniert dieses Programm? Als erstes fällt auf, das ein neuer Header hinzugekommen ist: #include “PGLight.h“ In diesem Header befinden sich die beiden vom GBAGFXCon angelegten Arrays mit der Palette und dem eigentlichen Bild. Auch die Zuweisung der Adressen hat sich geändert: u8* videobuffer = (u8*)0x6000000; u16* palettebuffer = (u16*)0x5000000; Neben der schon bekannten Adresse für den Bildspeicher ist nun noch eine Adresse für die Palette hinzugekommen. Immen dran denken, alles was Ihr in diesen Speicherbereich schreibt hat direkte Auswirkungen auf die Anzeige. Wir werden diesen Umstand noch ausnutzen. Der Variabelentyp des Bildspeichers ist nun nicht mehr u16 ( unsigned int short) sonder u8 (unsigned int char). Das liegt daran, das jetzt im Bildspeicher keine 16bit Farben mehr abgelegt werden, sondern nur noch die Einträge in die Palette. Und diese hat nur 256 Einträge, also jeder Eintrag hat 8Bit. Mit: SetMode(MODE_4 | BG2_ENABLE); Sagen wir den Gameboy, dass wir im Mode 4 arbeiten wollen. Anschließend werden alle Einträge der Palette in den Palettenspeicher eingetragen und alle Bilddaten in den Bildspeicher: for(i=0;i<256;i++) palettebuffer[i] = PGLight_Palette[i]; for(i=0;i<38400;i++) videobuffer[i] = PGLight_Map[i]; Die folgende Zeile: while((*KEYS) & KEY_A); sagt dem GBA, daß er warten soll bis die Taste A gedrückt wird. Eine genaue Beschreibung der Tasten und Eingaben werden wir in einem späteren Teil des Tutorials behandeln. while(1) { for(i=0;i<256;i++) if((i+j) < 255) palettebuffer[i] = PGLight_Palette[i+j]; j++; if(j==256) j=0; WAIT(3000); } Diese Schleife führt ein sogenanntes Palette-Looping durch. Dabei wird dem Palettenspeicher immer eine neue um einen Wert verschobene Palette zugewiesen. Bei geschickter Wahl der Palette und des Bildes kann man so interessante und vor allem schnelle Animationen erstellen, da der Bildschirm nicht neu gezeichnet werden muss. Insbesondere Aus- und Einblendeffekte werden so realisiert. Die Funktion WAIT()ist hier nur ein kleiner Zeitbegrenzer, der dem GBA sagt, das er 3000 Additionen durchführen soll, bevor er weiter macht. Wie Ihr seht ist auch der Mode 4 noch recht einfach zu handhaben.

Mode 5

Kommen wir nun zum letzten der einfachen Grafikmodi. Der Mode 5 ist eine Mischung aus Mode 3 und Mode 4. Genauso wie im Mode 3 besteht jedes Pixel aus einem 16Bit Farbwert. Dennoch verbraucht ein Bild im Mode 5 weniger Speicher als im Mode 3. Das liegt daran, dass jedes Bild hier nur 160*128 Pixel groß ist. Man hat also die Möglichkeit zwei Bilder im Videospeicher abzulegen bzw. ein Bild im Speicher zu zeichnen während das andere dargestellt wird. Das folgende Programm soll euch mit dem Verfahren des double bufferings bekannt machen. Dazu müssen wir zunächst die Adressen für das erste und das zweite Bild definieren:
0 | #define ErstesBild (u16*) 0x6000000
1 | #define ZweitesBild (u16*) 0x600A000
2 |
3 | u16* videobuffer;
Wie Ihr seht steht der Videospeicher hier nicht mehr an einer festen Adresse. Die Adressen sind nun durch die beiden Variablen ErstesBild und ZweitesBild vorgegeben. Des weiteren kommen zwei neue Funktionen vor. Beide Funktionen stammen von Dovoto und ich habe versucht Sie etwas verständlicher zu gestalten. Die erste Funktion:
0 | void WechsleBild(void)
1 | {
2 | if(REG_DISPCNT & BACKBUFFER)
3 | {
4 | REG_DISPCNT &= ~BACKBUFFER;
5 | videobuffer = ZweitesBild;
6 | }
7 | else
8 | {
9 | REG_DISPCNT |= BACKBUFFER;
10 | videobuffer = ErstesBild;
11 | }
12 | }
überprüft, welches Bild gerade angezeigt wird und wechselt dann das anzuzeigenden Bild. Je nachdem, ob das Statusbit BACKBUFFER ( in der gba.h definiert ) gesetzt ist oder nicht, setzt diese Funktion dieses Bit zurück und weist der Adresse des Videospeichers die Adresse des ersten oder zweiten Bildes zu. Die zweite Funktion:
0 | void WarteAufAustastluecke(void)
1 | {
2 | #define ScanlineCounter *(volatile u16*)0x4000006
3 |
4 | while(ScanlineCounter < 160);
5 | }
wartet, bis ein Bild fertiggezeichnet wird. Der Begriff Austastlücke ist hierbei nicht ganz so glücklich gewählt. Dieser Begriff stammt aus der Fernsehtechnik und bezeichnet ein bestimmtes Videosignal das dem Fernseher sagt, dass ein Bild dargestellt wurde. An der Adresse von ScanlineCounter speichert der GBA immer den Index der aktuellen Zeile, die er gerade zeichnet. Um also zu warten, bis ein Bild komplett gezeichnet wurde müssen wir warten bis der Wert von ScanlineCounter 160 geworden ist. Das Hauptprogramm sieht wieder recht einfach aus:
0 | int main(void)
1 | {
2 | int i;
3 | int Endpixel = 20480;
4 |
5 | SetMode(MODE_5 | BG2_ENABLE);
6 |
7 | videobuffer = ErstesBild;
8 | for(i=0;i videobuffer[i] = RGB(255,0,0);
9 |
10 | videobuffer = ZweitesBild;
11 | for(i=0;i videobuffer[i] = RGB(0,255,0);
12 |
13 | while(1)
14 | {
15 | WarteAufAustastluecke();
16 | WechsleBild();
17 | while((*KEYS) & KEY_A);
18 | }
19 | }
Hier zeichnen wir erst ein komplett rotes Bild in den Bildspeicher des ersten Bildes und danach ein komplett grünes Bild in den Bildspeicher des zweiten Bildes. Die Variable Endpixel ergibt sich daraus, dass im Mode 5 das Bild nur noch 160*128 = 20480 Pixel groß ist. Ihr könnt diese Variable beliebig vergrößern und werdet bis auf etwaige Abstürze nichts erleben. Das Programm startet danach eine Endlosschleife, die nichts anderes macht, als zwischen den beiden Bildern hin- und herzuwechseln, wenn Ihr die A-Taste des GBA drückt. Wenn Ihr die Funktion WarteAufAustastLuecke()weglast, so bekommt Ihr ein schönes Streifenmuster beim drücken der A-Taste. Es ist also beim double buffering absolut notwendig, daß Ihr immer wartet, bis ein Bild gezeichnet wurde.

Ein kleiner Bildbetrachter

Mit den nun geschilderten Funktionen der unterschiedlichen Grafikmodi ist es möglich einen kleinen Bildbetrachter zu schreiben. Dazu erstellen wir zunächst mit dem Programm AGBGFXCon 5 kleine Bilder und binden Sie in das Projekt ein. Beachtet, das Ihr die Dateien von .c nach .h umbenennen müsst und die zweite Zeile mit dem Include in jeder Date entfernen müsst. Zur Darstellung empfehle Ich den Mode 4. Die Bilder laden zwar fast zu schnell, aber man kommt mit weniger Speicher aus. Und man sollte sich gleich angewöhnen, nicht zu verschwenderisch mit dem Speicher umzugehen. Ich erspare es mir hier, das ganze Programm abzudrucken. Das Dokument würde dann einfach zu lang.

... und ich kann doch zählen

Wer sich jetzt vielleicht schon fragt, wo die übrigen Screenmodes geblieben muss leider auf das nächste mal warten. Die Modi 0 – 2 sind von den Modi 3 – 5 so verschieden, dass Sie ein eigenes Kapitel verdienen. Auch die Abfrage der Tastatur und die Steuerung von Interrupts können noch einige Kapitel füllen. So das war's erst mal. Ich hoffe ihr habt grob verstanden, worauf es bei der Verwendung von Screenmodes ankommt und könnt schon selbst ein wenig mit der Grafik des GBA herumexperimentieren.

Die Programme downloaden (Source + Rom)

Hier giebts noch die ganzen Programme zum download. Ich (Hurik =) Ich mach hier die Fummel arbeit!) hab sie mal geordnet ob sie mit oder ohne Microsoft Visual C++ gemacht worden sind.
Mit Microsoft Visual C++ | Ohne Microsoft Visual C++
Mode 3 | Mode 3
Mode 4 | Mode 4
Mode 5 | Mode 5
Bildbetrachter | Bildbetrachter
Achso noch was wichtiges! LuckyGeorg benutzt in seinem Tutorial (Das habt ihr grad gelesen!) hier andere Header Dateien als ich in meinem. Das ist sehr unpraktisch und könnte Anfänger verwirren. Tut es warscheinlich auch =) ! Sorry. Deswegen: Im nächsten Tut führen wir einheitliche Headers ein. Damit wir euch net verwirren also ladet sie euch runter. Aber was ihr hier gelernt habt bleibt so. lso es ändert sich nix.
(c) by Daniel "LuckyGeorge" Winkler

DevGBA
www.robsite.de