GP32 VBLANK

Aus portablegaming.de Wiki
Wechseln zu: Navigation, Suche

Dieser Artikel ist ein Artikel, der dem alten PortableDev zur Verfügung gestellt wurde. Er ist Eigentum des jeweiligen Autors und steht nicht unter der Creative Commons-Lizenz des Wikis!



Wozu überhaupt?

Vorlage:Box

Möglichkeiten

Vorlage:Box
Vorlage:Box Wichtig

Umsetzung

Als erstes haben wir da unseren Startup Code mit einigen Variablen und sonstigen Schmodder.

 #include "gp32.h"
 
 #include <stdio.h>      // für "printf"
 #include <stdarg.h>
 #include <stdlib.h>
 
 int x;
 volatile u16 a, b; 
 
 volatile long* lcdcon1 = (long*)0x14a00000;      // LCD Register
 
 // globals to save timer VSync Settings
 static int      _line;
 static long    _pclk;
 
 static u16      *framebuffer[2];            // unsere buffer
 static char    swapper=0;                   // swap   variable
 volatile s16  flip_now, flipper;            // Sagt unserem VBlank das er switchen darf

Ehe wir nun schließlich zur main() Funktion kommen.

 void main() {
    int x;
    u16 *buffer;
    
    gp_setCpuspeed(33);
       // returns the refresh rate
    initGFX();
    gp_clearFramebuffer16(framebuffer[0],0xFFff);
    gp_clearFramebuffer16(framebuffer[1],0xFFff);
    
    while (1) {
       if ( gp_getButton()&BUTTON_A) gp_Reset();
       buffer = flip();
       // Ausgaben ab hier
       //-------------------------------------------------------------------
  
       // Ausgabe vom letzten mal löschen
       for(x=0; x<=10; x++) {
          gp_drawLine16 (100, 10 + x, 200, 10 + x, 0xFFff, buffer);
       }
  
       // Debug Ausgabe
       wt_printf(10, 10, 0x000f,   buffer, "mein timer: %i", a++);
  
  
       //-------------------------------------------------------------------
    }
 }

Ich denke einmal der Code ist soweit so klar. Wir takten als erstes die CPU mit 33Mhz.
initGFX() ist unsere hauseigene Grafik initialisierung. Zu der kommen wir gleich noch.
Danach löschen wir den Front und den Backbuffer (ja, wir benutzen double Buffer) und gehen in die Hauptschleife die beim betätigen der A-Taste durch gp_Reset() verlassen wird.
flip(); ist unsere Funktion die dann wirklich zwischen den einzelnen Frames hin und her flippt. Ihre Rückgabe ist ein Pointer zum aktuellen Screen.
wt_printf() ist eine selbst erstellte printf Funktion. Macht was immer ihr hier auch wollt ^^




initGFX()

In unser Init Funktion gibt es nun einiges zu tun damit später alles richtig funktioniert. Zuerst einmal wird wie gewohnt der Bildschirm mit den beiden Buffern initialisiert.

  void initGFX() {
     int pclk;                  // pclk calc var
     int framerate;             // The true Framerate
     int line = 318;            // Blank Line
     int i;
  
   // Standart Setup
     for(i=0; i<2; i++) framebuffer[i] = (u16*) (FRAMEBUFFER + (0x25800*i));
     framerate = gp_initFramebuffer(framebuffer[0], 16, 80);      // 16bit colors. 80Hz

Soweit ist denke ich mal alles klar. Nun kümmern wir uns um den Interrupt und den Timer.

   // Interrupt Setup
     pclk = gp_getPCLK();
     pclk/= 16;                // clock divider 1/16
     pclk/= 256;               // prescaler 256
     pclk/= framerate;         // Framerate from above (should be 80Hz ..)
  
     _line = line;
     _pclk = pclk;
     
     gp_disableIRQ();                  // IRQ's stoppen
        gp_installSWIIRQ(12, VSync);   // IRQ installieren
     
        rTCFG0 = (0xFF<<8); // Presacler for timer 2,3,4 = 256
        rTCFG1 = (0x03<<8); // timer2 1/16
        
        // Wait for sync lines
        waitline(_line);
        
        rTCON = 0; //|= ~(1 << 12);                 // stop timer 2
           rTCNTB2 = rTCMPB2 = _pclk;               // Set Value + Compare Register
           rTCON |= (1 << 13);                      // preload
        rTCON = (rTCON & ~(1 << 13)) | (9 << 12);   // Restart !preload
  
     gp_enableIRQ();
  
     flip_now = -1;   // Start living
  }

Ich denke die Kommentare zu diesem Teil des Quellcodes sind recht vielsagend. Einzig die ganzen Timer Register mögen etwas verwirrend sein ... Doch dies liest man am besten in der Tech-Doc nach in der in aller Ausführlichkeit erklärt ist wie genau die Timer Register aufgebaut sind und wie man sie benutzt.

Eine Erwähnung wert ist vielleicht noch waitline() die wie der Name schon sagt wartet bis eine bestimmte Zeile im LCD gerendert wurde.

  inline void waitline(int line) {
     while (line != ((*lcdcon1 >> 18) & 0x1ff) );
  }

Am Ende schalten wir noch die globale Variable flip_now auf -1 um sozusagen den Stein ins Rollen zu bringen.


Der Interrupt

Oben haben wir bereits beim aktivieren des Timer Interrupts den namen 'VBlank benutzt. Zeit das wir diese Funktion nun auch wirklich erstellen.

 static void VSync(void) __attribute__ ((interrupt ("IRQ")));
 static void VSync(void) {
    // sync timer
    waitline(_line);
  
    rTCON = 0; //|= ~(1 << 12);                   // stop timer
    rTCON |= (1 << 13);                           // restart
    rTCON = (rTCON & ~(1 << 13)) | (9 << 12);     //      timer
  
    // Flipp Code
    if(flip_now!=-1) {
       gp_setFramebuffer(framebuffer[flip_now],   0);
       flip_now = -1;
    }
    flipper = 1;
 }

Tja. Sieht doch fast leichter aus als gedacht, oder?
Als erstes wird auf die richtige scanline gewartet. Das tun wir bei jedem Lauf egal ob nun etwas passieren soll oder nicht denn schalten wir den Timer nicht jedes mal gleich wird es leider schnell asyncron.

flip_now ist eine wichtige Variable. Ist sie <> 1 wird es zeit den Flipp durchzuführen.
flipper hingegen ist einfach nur eine Art Sicherung. Da der GeePee32 Emulator keine IRQ's unterstützt würde unser Programm hier sofort in eine Endlosschleife geraten.


flip()

 u16* flip() {
   //Zeige das JETZTIGE Bild   an.
   if(flipper==0) {
      // No IRQ - seems to be geepee32 or whatever
       gp_setFramebuffer(framebuffer[swapper],   1);
    }
    else {
       while(flip_now!=-1);      // wait for the LCD
       flip_now = swapper;
    }
  
   //Setze das NEUE Bild
    if(++swapper==2) swapper=0;
   //Gibt den pointer zurück
    return framebuffer[swapper];
 } 

An der Flip Funktion gibt es nicht viel Besonderes, der User ruft sie immer dann auf wenn er ein Bild zu Ende gezeichnet hat.
Für den Fall das wir uns in einem Emulator befinden der keine IRQ's Unterstützt benutzen wir einfach die original gp_setFramebuffer Funktion mit aktiviertem Sync-Flag. Der ganze Vorteil unseres VBlanks ist zwar so dahin aber immerhin funktioniert unser Programm noch.

flip_now gibt uns zu erkennen ob schon alles bereit ist für den nächsten Flip. Für den sehr unwarscheinlichen Fall, dass wir schneller gerendert haben als der LCD gezeichnet hat warten wir an dieser Stelle bis der LCD fertig ist.

Danach wird das double Buffering überprüft und der neue Screen zurück gegeben.


Schlusswort

Vorlage:Box

--Tharo 20:43, 28. Feb 2005 (UTC)