Tipps und Tricks

FreeBASIC-Referenz » Die Gfxlib » Tipps und Tricks

Achtung: Dieser Abschnitt richtet sich an fortgeschrittene Programmierer, die einen tieferen Einblick in die Verwaltung von Grafikpuffern erhalten wollen. Er behandelt folgende Themen:

Double Buffering
Vermeiden Sie Double Buffering (ein Bild zuerst in einen Puffer schreiben, und es dann in den Video-RAM kopieren) soweit wie möglich. Die Gfx-Lib benutzt bereits ein Double-Buffering-System, um den Bildschirm zu aktualisieren. Wenn Sie Ihren eigenen Zweitpuffer verwenden, müssen nur weitere (unnötige) Bildschirmkopien durchgeführt werden, wodurch Ihr Programm langsamer wird. Stattdessen sollten Sie die bereits unterstützten Flipping-Funktionen (Verwendung mehrerer Bildschirmseiten; siehe SCREENSET und das Beispiel von MULTIKEY) benutzen.

Arten der Bildpuffer
GET und PUT unterstützen neben normalen Arrays auch Pointer als Bildpuffer:

TYPE FB_IMAGE FIELD = 1
   width AS USHORT
   height AS USHORT
   imageData(64000) AS UBYTE
END TYPE

DIM udt AS FB_IMAGE
DIM udt_ptr AS FB_IMAGE PTR
DIM array(16008) AS INTEGER
DIM array_ptr AS INTEGER PTR

udt_ptr = @udt
array_ptr = @array(0)

SCREENRES 320, 200

' Diese Anweisungen haben dieselbe Funktion
GET (0, 0)-(319, 199), array
GET (0, 0)-(319, 199), array(0)
GET (0, 0)-(319, 199), @array(0)
GET (0, 0)-(319, 199), array_ptr
GET (0, 0)-(319, 199), @array_ptr[0]

' Diese Anweisungen sind ebenfalls äquivalent zueinander
GET (0, 0)-(319, 199), @udt
GET (0, 0)-(319, 199), udt_ptr
GET (0, 0)-(319, 199), @udt_ptr[0]
SLEEP

In diesem Beispiel wurde GET verwendet, es funktioniert aber ebenso mit PUT.

Bildpuffer bei Drawing Primitives
Alle Drawing Primitives (einfachste Grafikanweisungen wie LINE, CIRCLE usw.) können an Grafikpuffer angewandt werden. Dadurch ist es für Sie möglich, auf eine beliebige Anzahl von nicht-Bildschirm-Oberflächen zu zeichnen, und diese später auf den Bildschirm zu übertragen:

DIM buffer(9602) AS USHORT
DIM sprite(1028) AS USHORT
SCREENRES 320, 200

' Da als Puffer ein GET/PUT-Puffer angenommen wird,
' setzen wir seine ersten vier Bytes so, dass sie einen
' normalen GET/PUT-Puffer-Header bilden.
buffer(0) = 160 SHL 3   ' Breite * 8
buffer(1) = 120     ' Höhe

CIRCLE buffer, (16, 16), 15, 12, , , 1, F
GET    buffer, ( 0,  0)-( 31,  31), sprite
PSET   sprite, (16, 16), 15
LINE   buffer, ( 0,  0)-(159, 119), 2, B
PUT    buffer, (50, 50), sprite, PSET
PUT            (80, 40), buffer, PSET
SLEEP

Einen einfacheren Weg, einen solchen Grafikpuffer zu erstellen stellt die IMAGECREATE-Funktion dar. Durch sie wird automatisch ein Puffer reserviert und initialisiert. Das obige Beispiel könnte dadurch so aussehen:

DIM buffer AS ANY PTR
DIM sprite AS ANY PTR

SCREENRES 320, 200

buffer = IMAGECREATE(160, 120)
sprite = IMAGECREATE(32, 32)

CIRCLE buffer, (16, 16), 15, 12, , , 1, F
GET    buffer, ( 0,  0)-( 31,  31), sprite
PSET   sprite, (16, 16), 15
LINE   buffer, ( 0,  0)-(159, 119), 2, B
PUT    buffer, (50, 50), sprite, PSET
PUT            (80, 40), buffer, PSET

IMAGEDESTROY buffer
IMAGEDESTROY sprite
SLEEP

Wenn Sie auf einen Puffer zeichnen, werden die Koordinaten durch den letzten Aufruf von WINDOW beeinflusst, jedoch nicht von VIEW. Die Clipping-Grenzen werden auf die Gesamtgröße des Puffers gesetzt. Natürlich kann der optionale Zielpuffer-Parameter bei allen Drawing Primitives angegeben werden und darf sowohl ein Array als auch ein Pointer sein, wie im Falle des GET/PUT-Bildpuffers.

GfxPrint
Sie können die Funktion der PRINT-Routine durch eine eigene Prozedur erweitern, die die vordefinierten Gfxlib-fonts verwendet. Das folgende Beispiel enthält die SUB GfxPrint, die das Zeichnen von transparentem Text an jeder Position sowie Clipping-Grenzen unterstützt; ebenso ist es möglich, in einen Bildpuffer zu schreiben.
Anmerkung: Dieses Beispiel funktioniert nur mit FreeBASIC-Versionen <= v0.15
Ab FB-Version 0.16 gibt es die Funktion DRAW STRING. Für neuere FreeBASIC-Versionen gibt es die DrawString-Routine (Ersatz für GfxPrint)

DECLARE SUB GfxPrint( BYREF text AS STRING, _
   BYVAL x AS INTEGER, BYVAL y AS INTEGER, _
   BYVAL col AS INTEGER, BYVAL buffer AS ANY PTR = 0 )

TYPE fb_FontType
   h AS INTEGER
   data AS UBYTE PTR
END TYPE

' Entfernen Sie das Kommentar-Zeichen vor dem
' Zeichenformat, das Sie benutzen wollen
'EXTERN fb_FontData ALIAS "fb_font_8x8" AS fb_FontType
'EXTERN fb_FontData ALIAS "fb_font_8x14" AS fb_FontType
'EXTERN fb_FontData ALIAS "fb_font_8x16" AS fb_FontType

SUB GfxPrint( BYREF text AS STRING, _
   BYVAL x AS INTEGER, BYVAL y AS INTEGER, _
   BYVAL col AS INTEGER, BYVAL buffer AS ANY PTR = 0 )

   DIM row AS INTEGER, i AS INTEGER
   DIM bits AS UBYTE PTR

   FOR i = 1 TO LEN(text)
      bits = fb_FontData.data + (ASC(MID$(text, i, 1)) * _
        fb_FontData.h)
      FOR row = 0 TO fb_FontData.h-1
         IF (buffer) THEN
            LINE buffer, (x + 7, y + row)-(x, y + row), col, _
               , *bits SHL 8
         ELSE
            LINE (x + 7, y + row)-(x, y + row), col, , *bits _
               SHL 8
         END IF
         bits += 1
      NEXT row
      x += 8
   NEXT i
END SUB

SCREENRES 320, 200
GfxPrint "Hello world!", 112, 96, 15
SLEEP

Achtung: Die GfxLib speichert alle Font- und Paletten-Daten in einem LZW-komprimierten Format, um Ihre EXEs klein zu halten, und dekomprimiert sie erst mit dem ersten SCREENRES-Aufruf. Wenn Sie SCREENRES in Ihrem Programm nie aufrufen, werden Zugriffe auf die Palette- und Font-Daten unbrauchbare Daten zurückliefern.

LZW-Codec in der GfxLib
Die GfxLib speichert die Paletten- und Font-Daten in einem LZW-komprimierten Format und entpackt diese erst, wenn zum ersten mal SCREENRES aufgerufen wird. Der LZW-Codec ist in Ihren Programmen nicht aufrufbar; er wurde nicht als eigener Befehl eingebaut. Es ist dennoch möglich, auf die Kompressionsfunktionen zuzugreifen, indem Sie die Funktionen einfach deklarieren; die GfxLib muss jedoch durch einen SCREENRES-Aufruf aktiviert sein.

Beispiel:

DECLARE FUNCTION LZW_Encode _
   ALIAS "fb_hEncode" ( _
   BYVAL in_buffer  AS ANY PTR, _
   BYVAL in_size    AS INTEGER, _
   BYVAL out_buffer AS ANY PTR, _
   BYREF out_size   AS INTEGER _
   ) AS INTEGER

DECLARE FUNCTION LZW_Decode _
   ALIAS "fb_hDecode" ( _
   BYVAL in_buffer  AS ANY PTR, _
   BYVAL in_size    AS INTEGER, _
   BYVAL out_buffer AS ANY PTR, _
   BYREF out_size   AS INTEGER _
   ) AS INTEGER

DIM src_buffer(100000) AS UBYTE
DIM src_size AS INTEGER
DIM dest_buffer(100000) AS UBYTE
DIM dest_size AS INTEGER

' funktioniert nur, wenn die GfxLib verwendet wird
SCREENRES 400, 300

src_size = 0
OPEN "gfxlib.txt" FOR BINARY AS #1
WHILE NOT EOF(1)
   GET #1,, src_buffer(src_size)
   src_size += 1
WEND
CLOSE #1
PRINT "Data size before compression:", src_size
GOSUB ShowData

dest_size = 100000
PRINT "Compressing...";
LZW_Encode @src_buffer(0), src_size, _
   @dest_buffer(0), dest_size
PRINT "done."

PRINT "Data size after compression:", dest_size
PRINT

src_size = 100000
PRINT "Decompressing...";
LZW_Decode @dest_buffer(0), dest_size, _
   @src_buffer(0), src_size
PRINT "done."

PRINT "Data size before decompression:", src_size
GOSUB ShowData
SLEEP
END

ShowData:
   DIM i AS INTEGER
   PRINT "Contents: ";
   FOR i = 3 TO 36: PRINT CHR$(src_buffer(i)); : NEXT
   PRINT " [...]"
RETURN

OpenGL-Textur
Das Erstellen einer OpenGL-Textur mit einem GET/PUT-Puffer ist einfach; hier ist ein kleiner Codeauszug, der diese Aufgabe erledigt:

#DEFINE TEX_MASKED   &h1
#DEFINE TEX_MIPMAP   &h2
#DEFINE TEX_NOFILTER &h4
#DEFINE TEX_HASALPHA &h8

FUNCTION CreateTexture( BYVAL buffer AS ANY PTR, _
   BYVAL flags AS INTEGER = 0 ) AS GLuint
   REDIM dat(0) AS UBYTE
   DIM p AS UINTEGER PTR, s AS USHORT PTR
   DIM AS INTEGER w, h, x, y, col
   DIM tex AS GLuint
   DIM AS GLenum format, minfilter, magfilter

   CreateTexture = 0

   s = buffer
   w = s[0] SHR 3
   h = s[1]

   IF( (w < 64) OR (h < 64) ) THEN
      EXIT FUNCTION
   END IF
   IF( (w AND (w-1)) OR (h AND (h-1)) ) THEN
      '' Width/height not powers of 2
      EXIT FUNCTION
   END IF

   REDIM dat(w * h * 4) AS UBYTE
   p = @dat(0)

   glGenTextures 1, @tex
   glBindTexture GL_TEXTURE_2D, tex

   FOR y = h-1 TO 0 STEP -1
      FOR x = 0 TO w-1
         col = POINT(x, y, buffer)
         ' Swap R and B so we can use the GL_RGBA texture format
         col = RGBA(col AND &hFF, _
            (col SHR  8) AND &hFF, _
            (col SHR 16) AND &hFF, _
             col SHR 24)
         IF( flags AND TEX_HASALPHA ) THEN
            *p = col
         ELSE
            IF( (flags AND TEX_MASKED) AND (col = &hFF00FF) ) THEN
               *p = 0
            ELSE
               *p = col OR &hFF000000
            END IF
         END IF
         p += 4
      NEXT x
   NEXT y

   IF( flags AND ( TEX_MASKED OR TEX_HASALPHA ) ) THEN
      format = GL_RGBA
   ELSE
      format = GL_RGB
   END IF

   IF( flags AND TEX_NOFILTER ) THEN
      magfilter = GL_NEAREST
   ELSE
      magfilter = GL_LINEAR
   END IF

   IF( flags AND TEX_MIPMAP ) THEN
      gluBuild2DMipmaps GL_TEXTURE_2D, format, w, _
         h, GL_RGBA, GL_UNSIGNED_BYTE, @dat(0)
      IF( flags AND TEX_NOFILTER ) THEN
         minfilter = GL_NEAREST_MIPMAP_NEAREST
      ELSE
         minfilter = GL_LINEAR_MIPMAP_LINEAR
      END IF
   ELSE
      glTexImage2D GL_TEXTURE_2D, 0, format, w, _
         h, 0, GL_RGBA, GL_UNSIGNED_BYTE, @dat(0)
      minfilter = magfilter
   END IF
   glTexParameteri GL_TEXTURE_2D, _
      GL_TEXTURE_MIN_FILTER, minfilter
   glTexParameteri GL_TEXTURE_2D, _
      GL_TEXTURE_MAG_FILTER, magfilter

   CreateTexture = tex
END FUNCTION