Jedną z podstawowych cech biblioteki OpenGL jest jest rozszerzalność. Tworzenie rozszerzeń (ang. extensions) pozwala na korzystanie z najnowszych rozwiązań w grafice komputerowej. Rozszerzenia opracowywane są przez różne firmy w tym oczywiście produkujące procesory graficzne.
Tworzenie rozszerzeń przez wiele firm wymaga odpowiedniej koordynacji. Jej brak szybko doprowadziłby do powstania konfliktów nazw funkcji i stałych. Nad tym procesem czuwa organizacja ARB, która prowadzi oficjalny rejestr rozszerzeń, dostępny pod adresem:
http://www.opengl.org/registry/.
Powstają także rozszerzenia eksperymentalne oraz inne, które nie są rejestrowane w oficjalnym rejestrze. Najbardziej popularne rozszerzenia obsługiwane są w wielu implementacjach biblioteki OpenGL. Starannie wybrane rozszerzenia ARB uwzględnia w specyfikacjach biblioteki.
Także biblioteka GLU oraz biblioteki narzędziowe specyficzne dla konkretnych systemów operacyjnych (np. GLX, WGL, AGL) posiadają swoje odrębne rozszerzenia.
Implementacja biblioteki OpenGL
Podstawowych informacji o implementacji biblioteki OpenGL dostarcza funkcja:
const GLubyte * glGetString( GLenum name )
Parametr name określa rodzaj informacji zwracanej przez funkcję i przyjmuje jedną z poniższych wartości:
Funkcja
glGetString w każdym przypadku zwraca ciąg znaków ASCII zakończony zerem (ASCIIZ). Nazwy obsługiwanych rozszerzeń biblioteki OpenGL oddzielone są pojedynczą spacją.
Warto zwrócić uwagę, że w systemach Microsoft Windows funkcja glGet- String, wywołana z parametrem GL EXTENSIONS, poda także obsługiwane rozszerzenia biblioteki WGL.
Także biblioteka GLU (od wersji 1.1) posiada mechanizm pozwalający na odczytanie numeru wersji i listy dostępnych rozszerzeń. Służy to tego funkcja:
const GLubyte * gluGetString( GLenum name )
której parametr name przyjmuje jedną z dwóch wartości: GLU VERSION i GLU - EXTENSIONS. Ich znaczenie oraz format zwracanych danych jest taki sam jak w przypadku funkcji glGetString.
Biblioteka GLUT umożliwia sprawdzenie czy implementacja biblioteki
OpenGL obsługuje określone rozszerzenie OpenGL. Służy do tego funkcja:
int glutExtensionSupported( const char * name )
która zwraca wartość GL TRUE jeżeli rozszerzenie o nazwie name jest obsługiwane przez implementację OpenGL. W przeciwnym wypadku funkcja zwraca wartość GL FALSE. Funkcja glutExtensionSupported nie obsługuje rozszerzeń biblioteki GLU oraz bibliotek narzędziowych specyficznych dla systemów operacyjnych.
Funkcję sprawdzającą dostępność rozszerzenia zawiera także biblioteka
GLU w wersji 1.3. Jest to funkcja:
GLboolean gluCheckExtension( const GLubyte * extName, const GLubyte * extString )
gdzie extName to nazwa sprawdzanego rozszerzenia, a extString ciąg zna- ków z dostępnymi rozszerzeniami.
Przykładowy program
Przykładowy program (plik wersja opengl.cpp) wyświetla wszystkie informacje o bibliotece OpenGL i GLU dostępne za pomocą funkcji glGetString i gluGetString. Rysunek 1 przedstawia wynik działania programu na komputerze autora działającym pod kontrolą systemu Microsoft Windows XP Home. Warto zauważyć, że Microsoft nie udostępnia najnowszej wersji biblioteki GLU. Możliwości bibliotek OpenGL i GLU tego samego komputera działającego pod kontrolą systemu operacyjnego Mandriva Linux LE2005 przedstawia rysunek 2. Dostępna jest nie tylko nowsza wersja OpenGL (1.4), ale także najnowsza wersja biblioteki GLU. Oczywiście nie oznacza, to że karta graficzna ATI Radeon 9000 obsługuje sprzętowo bibliotekę OpenGL w wersji 1.4 - część funkcji dostępnych od wersji 1.4 jest emulowana przez bibliotekę Mesa na drodze programowej.
Sam projekt Mesa (
http://www.mesa3d.org/) wart jest odrobiny uwagi. Jest to całkowicie programowa implementacja biblioteki OpenGL, autorstwa Briana Paula. Najnowsza wersja Mesy 6.4 (październik 2005 roku) jest zgodna z OpenGL 1.5. Dzięki otwartej architekturze i dostępności kodu źródłowego biblioteka Mesa działa na praktycznie każdym systemie operacyjnym, a specjalne sterowniki umożliwiają sprzętową akcelerację OpenGL w wielu systemach operacyjnych. Mesa działa oczywiście także w systemach
Microsoft Windows - efekt działania przykładowego programu na komputerze autora przedstawia rysunek 3.
Dla porównania na rysunku 4 przedstawiono efekt działania tego samego programu na nowym komputerze autora zawierającym kartę graficzną ATI Radeon X700. Poza obsługą biblioteki OpenGL w wersji 2.0 zwraca uwagę dodanie obsługi rozszerzeń SSE2 w sterowniku. Tego rodzaje optymalizacje są powszechnie stosowane przez producentów sterowników do kart graficznych i mają oczywiście wpływ na szybkość ich działania.
Plik wersja opengl.cpp
#include <GL/glut.h>
#include <stdlib.h>
#include <string.h>
enum
{
EXIT
};
void DrawString( int x, int y, char * string )
{
glRasterPos2i( x, y );
int len = strlen( string );
for( int i = 0; i < len; i++ )
glutBitmapCharacter( GLUT_BITMAP_9_BY_15, string[ i ] );
}
void Display()
{
glClearColor( 1.0, 1.0, 1.0, 1.0 );
glClear( GL_COLOR_BUFFER_BIT );
glColor3f( 0.0, 0.0, 0.0 );
char string[ 2048 ];
GLuint height = glutGet( GLUT_WINDOW_HEIGHT ) - 15;
GLuint width = glutGet( GLUT_WINDOW_WIDTH );
strcpy( string, "GL_VENDOR: " );
strcat( string,( char * ) glGetString( GL_VENDOR ) );
DrawString( 0, height, string );
height -= 16;
strcpy( string, "GL_RENDERER: " );
strcat( string,( char * ) glGetString( GL_RENDERER ) );
DrawString( 0, height, string );
height -= 16;
strcpy( string, "GL_VERSION: " );
strcat( string,( char * ) glGetString( GL_VERSION ) );
DrawString( 0, height, string );
height -= 16;
const GLubyte * extstr = glGetString( GL_EXTENSIONS );
strcpy( string, "GL_EXTENSIONS: " );
int extpos = 0;
int extlen = strlen(( char * ) extstr );
while( extpos < extlen )
{
int pos = extpos;
while( extstr[ pos ] != '\0' && extstr[ pos ] != ' ' )
pos++;
if( 9 *( pos - extpos + strlen( string ) ) < width )
{
strncat( string,( char * ) & extstr[ extpos ], pos - extpos + 1 );
extpos = pos + 1;
}
else
{
DrawString( 0, height, string );
height -= 16;
strcpy( string, " " );
strncat( string,( char * ) & extstr[ extpos ], pos - extpos + 1 );
extpos = pos + 1;
}
if( extpos == extlen )
{
DrawString( 0, height, string );
height -= 16;
}
}
strcpy( string, "GLU_VERSION: " );
strcat( string,( char * ) gluGetString( GLU_VERSION ) );
DrawString( 0, height, string );
height -= 16;
extstr = gluGetString( GLU_EXTENSIONS );
strcpy( string, "GLU_EXTENSIONS: " );
extpos = 0;
extlen = strlen(( char * ) extstr );
while( extpos < extlen )
{
int pos = extpos;
while( extstr[ pos ] != '\0' && extstr[ pos ] != ' ' )
pos++;
if( 9 *( pos - extpos + strlen( string ) ) < width )
{
strncat( string,( char * ) & extstr[ extpos ], pos - extpos + 1 );
extpos = pos + 1;
}
else
{
DrawString( 0, height, string );
height -= 16;
strcpy( string, " " );
strncat( string,( char * ) & extstr[ extpos ], pos - extpos + 1 );
extpos = pos + 1;
}
if( extpos >= extlen )
{
DrawString( 0, height, string );
height -= 16;
}
}
glFlush();
glutSwapBuffers();
}
void Reshape( int width, int height )
{
glViewport( 0, 0, width, height );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
gluOrtho2D( 0, width - 1, 0, height - 1 );
Display();
}
void Menu( int value )
{
if( value == EXIT )
exit( 0 );
}
int main( int argc, char * argv[] )
{
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
glutInitWindowSize( 600, 600 );
glutCreateWindow( "Wersja OpenGL" );
glutDisplayFunc( Display );
glutReshapeFunc( Reshape );
glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
glutMainLoop();
return 0;
}
Specyfikacje rozszerzeń
Specyfikacje zarejestrowanych rozszerzeń biblioteki OpenGL i bibliotek narzędziowych dostępne są pod adresem:
http://www.opengl.org/registry/. Budowa specyfikacji rozszerzenia jest ściśle określona i składa się z poniżej opisanych części. Czytając specyfikacje rozszerzeń trzeba pamiętać, że są ona tworzone przez autorów implementacji OpenGL głównie dla autorów innych implementacji tej biblioteki. Stąd lektura tych dokumentów wymaga zazwyczaj dobrej znajomości biblioteki OpenGL.
Jako ciekawostkę można dodać, że specyfikacja rozszerzenia ma postać pliku tekstowego ASCII o maksymalnej szerokości wiersza wynoszącej 72 znaki. W przypadku tabel maksymalna szerokość wiersza nie może przekraczać 132 znaków. Każda linia ma być zakończona znakiem końca wiersza, a cały dokument nie może zakładać żadnego niestandardowego formatowania.
Nazwa
Formalna nazwa rozszerzenia zaczyna się prefiksem określającym autora rozszerzenia, przykładowo „SGI” lub „IBM”. Rozszerzenia opracowywane przez grupę firm posługują się zazwyczaj prefiksem „EXT”. Natomiast rozszerzenia zaaprobowane przez ARB otrzymują prefiks „ARB”. Po prefiksie znajduje się właściwa nazwa rozszerzenia, pisana małymi literami, ze słowami oddzielonymi podkreśleniami. Prefiks od właściwej nazwy rozszerzenia także oddziela znak podkreślenia.
Identyfikatory
Identyfikator rozszerzenia to jego nazwa poprzedzona prefiksem określającym jakiej biblioteki dotyczy dane rozszerzenie. W przypadku biblioteki OpenGL będzie to prefiks „GL”, biblioteki GLU prefiks „GLU”, a bibliotek narzędziowych prefiks odpowiadający jej nazwie.
Czasami rozszerzenie obejmuje jednocześnie kilku bibliotek. Przykładowo specyfikacja rozszerzenia ARB multisample wprowadziła zmiany do bibliotek OpenGL, GLX i WGL i w związku z tym zawiera trzy identyfikatory: GL ARB multisample, GLX ARB multisample i WGL ARB multisample.
Kontakt
Ta część specyfikacji rozszerzenia zawiera dane osób i firm, które opracowały rozszerzenie. Zazwyczaj są to imię i nazwisko autora, nazwa firmy oraz adres poczty e-mail.
Status
Status określa etap rozwoju specyfikacji rozszerzenia. Typowe statusy to rozszerzenia kompletne („Complete”) i przestarzałe („Obsolete”). Rozszerzenia w trakcie rozwoju zawierają zwykle określenia typu: „XXX - Not complete yet!!!”. Publicznie publikowane wersje rozwojowe specyfikacji otrzymują zazwyczaj status „Shipping” wraz z numerem wersji. W przypadku, gdy rozszerzenie zostało zaaprobowane przez ARB w tej części specyfikacji znajduje się data przyjęcia przez ARB.
Wersja
Ta część zawiera informacje o wersji rozszerzenia, dacie ostatniej modyfikacji specyfikacji oraz ewentualne powiązania z innymi rozszerzeniami.
Numer
Rozszerzenia ujęte w rejestrze prowadzonym przez ARB uzyskują unikatowy numer. Odrębna numeracja prowadzona jest dla rozszerzeń zaaprobowanych przez ARB.
Zależności
Ta część specyfikacji zwiera wymagania dotyczące wersji biblioteki OpenGL oraz jej rozszerzeń niezbędnych do prawidłowego działania prezentowanego rozszerzenia. Także w tej części specyfikacji zamieszczona jest informacja o ewentualnym uwzględnieniu rozszerzenia w specyfikacji OpenGL.
Omówienie
Ogólne informacje o rozszerzeniu, jego funkcjach i oferowanych możliwościach.
Własności intelektualne
Ta część specyfikacji określa czy rozwiązania i techniki zastosowane w rozszerzeniu nie są objęte patentem lub innymi ograniczeniami prawnymi.
Zagadnienia
Wykaz zagadnień i rozważań związanych z rozszerzeniem oraz dokonanych wyborów rozwiązań i powodów ich wyboru. Zazwyczaj opis zamieszczony w tej części specyfikacji ma postać zestawu pytań i odpowiedzi.
Nowe procedury i funkcje
Wykaz wszystkich procedur i funkcji zdefiniowanych w rozszerzeniu. Podobnie jak w specyfikacji biblioteki OpenGL funkcje prezentowane są bez przedrostka gl (odpowiednio glu, wgl, itd.), zgodnie ze składnią obowiązującą w języku C.
Nowe typy
Wykaz nowych typów zmiennych zdefiniowanych w rozszerzeniu. Obowiązuje składnia języka C.
Nowe stałe
Wykaz wszystkich nowych stałych zdefiniowanych w rozszerzeniu. Stałe dotyczące biblioteki OpenGL są prezentowane bez przedrostka GL , stałe dotyczące innych bibliotek prezentowane są zazwyczaj z odpowiednimi przedrostkami.
Uzupełnienia do specyfikacji OpenGL i innych bibliotek
Zawartość tej części specyfikacji rozszerzenia zależy oczywiście od jego charakteru. Rozszerzenie może dotyczyć poszczególnych rozdziałów i dodatków specyfikacji biblioteki OpenGL oraz bibliotek narzędziowych (AGL/ EGL/GLX/WGL), a także specyfikacji języka GLSL. Ta część rozszerzenia, po aprobacie ARB może zostać włączona do specyfikacji OpenGL lub bibliotek narzędziowych.
Błędy
Wykaz błędów generowanych przez funkcje i procedury dodane lub zmodyfikowane w rozszerzeniu.
Nowe zmienne maszyny stanu
Ta część specyfikacji zawiera wykaz nowych zmiennych maszyny stanów biblioteki OpenGL oraz funkcji pozwalających na odczyt wartości tych zmiennych (rozdział 6 specyfikacji OpenGL). Wykaz prezentowany jest w układzie tabelarycznym.
Nowe zmienne maszyny stanu zależne od implementacji
Tabelaryczny wykaz zmiennych maszyny stanu biblioteki OpenGL, które zależą od implementacji OpenGL wraz z funkcjami pozwalającym na odczyt wartości tych zmiennych.
Przykładowy kod
Stosunkowo rzadko spotykany w specyfikacji rozszerzeń przykładowy kod źródłowy (najczęściej w języku C) ilustrujący sposób użycia rozszerzenia.
Testy zgodności
Przykłady testów umożliwiających sprawdzenie zgodności implementacji rozszerzenia ze specyfikacją.
Historia korekt
Wykaz wszystkich korekt i zmian wprowadzanych w rozszerzeniu, z podaniem daty oraz numeru wersji rozszerzenia.
Używanie rozszerzeń
Czytelnik wie już jak sprawdzić, którą wersję biblioteki OpenGL obsługuje używana implementacja i jakie dostępne są rozszerzenia biblioteki OpenGL. Teraz dowiemy się jak używać rozszerzeń biblioteki OpenGL w systemach Microsoft Windows, Linux (UNIX) oraz MAC OS X.
Standardowe pliki nagłówkowe
Pliki nagłówkowe biblioteki OpenGL i GLU to odpowiednio gl.h i glu.h. Identyfikację, której wersji biblioteki OpenGL odpowiada plik nagłówkowy gl.h umożliwiają definicje preprocesora umieszczone w tym pliku:
#define GL_VERSION_1_1 1
#define GL_VERSION_1_2 1
#define GL_VERSION_1_3 1
#define GL_VERSION_1_4 1
#define GL_VERSION_1_5 1
#define GL_VERSION_2_0 1
#define GL_VERSION_2_1 1
Taki sam mechanizm identyfikowania wersji dotyczy pliku nagłówkowego glu.h biblioteki GLU. W tym wypadku możliwe są trzy definicje preprocesora:
#define GLU_VERSION_1_1 1
#define GLU_VERSION_1_2 1
#define GLU_VERSION_1_3 1
Wystąpienie danej definicji w pliku gl.h oznacza, że zawiera on definicje wszystkich funkcji, zmiennych, stałych i typów opisanych w odpowiedniej wersji specyfikacji biblioteki OpenGL. Taka sama reguła dotyczy biblioteki pliku glu.h i biblioteki GLU. Przykładowo Microsoft udostępnia pakiet OpenGL API który zawiera m.in. pliki nagłówkowe gl.h i glu.h. Plik gl.h odpowiada oczywiście wersji 1.1 biblioteki OpenGL, a plik glu.h wersji 1.2 biblioteki GLU. Z kolei te same pliki dołączone do biblioteki Mesa w wersji 6.X odpowiadają specyfikacji OpenGL 1.3 i GLU w wersji 1.3.
Pliki nagłówkowe rozszerzeń
Rejestr rozszerzeń OpenGL, GLU i bibliotek narzędziowych zawiera, poza specyfikacjami poszczególnych rozszerzeń, także specjalne SDK z trzema plikami nagłówkowymi: glext.h, glxext.h i wglext.h. Pierwszy plik dotyczy zarejestrowanych rozszerzeń biblioteki OpenGL, drugi zarejestrowanych rozszerzeń biblioteki GLX, trzeci zarejestrowanych rozszerzeń biblioteki WGL. Plik glext.h zawiera poza definicjami specyficznymi dla wszystkich zarejestrowanych rozszerzeń OpenGL także definicje funkcji, stałych i typów biblioteki OpenGL od wersji 1.2, aż do najnowszych. Potencjalne konflikty związane z wielokrotnym występowaniem tych samych definicji w plikach gl.h i glext.h eliminowane są poprzez wykorzystanie opisanych w poprzednim punkcie definicji preprocesora. Taki sam mechanizm wykorzystywany jest we wszystkich plikach nagłówkowych rozszerzeń do eliminacji potencjalnego zwielokrotnienia definicji specyficznych dla poszczególnych rozszerzeń. W tym wypadku używane są definicje preprocesora wykorzystujące identyfikatory (nie nazwy!) rozszerzeń. Domyślnie dołączenie plików nagłówkowych rozszerzeń udostępnia programiście wszystkie zawarte w nich definicje funkcji, stałych i typów.
Popatrzmy na definicje przykładowego rozszerzenia SUNX constant data, które tak jak definicje pozostałych rozszerzeń podzielone są na dwie grupy. Pierwsza grupa zawiera definicje stałych opisanych w specyfikacji rozszerzenia:
#ifndef GL_SUNX_constant_data
#define GL_UNPACK_CONSTANT_DATA_SUNX 0x81D5
#define GL_TEXTURE_CONSTANT_DATA_SUNX 0x81D6
#endif
Druga grupa zawiera definicje funkcji opisanych w rozszerzeniu:
#ifndef GL_SUNX_constant_data
#define GL_SUNX_constant_data 1
#ifdef GL_GLEXT_PROTOTYPES
GLAPI void APIENTRY glFinishTextureSUNX( void );
#endif
typedef void( APIENTRYP PFNGLFINISHTEXTURESUNXPROC )( void );
#endif
Ta druga część definicji z pliku glext.h wymaga dłuższego opisu. Składa się ona z dwóch części. Pierwsza zawiera typową definicję (prototyp) funkcji zewnętrznej. Identyfikator GLAPI jest zdefiniowany jako extern, a APIENTRY jest identyfikatorem zależnym od systemu operacyjnego (standardowo definicja ta jest pusta). Druga część to definicja wskaźnika do funkcji. Identyfikator APIENTRYP jest zależny od systemu operacyjnego (standardowa definicja to *). Warto ponadto zauważyć, że druga część definicji związanych z rozszerzeniem SUNX constant data tworzy nowa definicję preprocesora (GL SUNX constant data), co eliminuje potencjalne konflikty nazw, a wybór rodzaju definicji funkcji uzależniony jest od występowania definicji preprocesora GL GLEXT PROTOTYPES. Standardowo nie jest ona zdefiniowana czyli programista otrzymuje do użytku wskaźniki na funkcje.
Jeżeli chcemy skorzystać z rozszerzeń wprowadzające jedynie nowe stałe lub typy wystarczy dołączenie do programu pliku nagłówkowego glext.h (względnie glxext.h lub wglext.h jeżeli korzystamy z rozszerzeń bibliotek GLX lub WGL). Problem stanowią rozszerzenia zawierające definicje nowych funkcji. Jego rozwiązanie wymaga krótkiego wprowadzenia do sposobu obsługi biblioteki OpenGL w poszczególnych systemach operacyjnych.
OpenGL w systemach Microsoft Windows
Każdy system operacyjny z rodziny Microsoft Windows począwszy od Windows NT 4.0 i Windows 95 posiada programową implementację biblioteki OpenGL zgodną z wersją 1.1, obsługującą rozszerzenia: GL EXT bgra, GL WIN swap hint i GL EXT paletted texture. Zgodnie ze swoją naturą implementacja ta nie korzysta ze wsparcia sprzętowego karty graficznej. Fizycznie implementacja OpenGL znajduje się w bibliotece opengl32.dll.
Wykorzystanie możliwości udostępnianych przez współczesne procesory graficzne odbywa się za pośrednictwem tzw. instalowalnego sterownika klienta ICD (ang. Installable Client Driver), który automatycznie przekierowuje wywołania funkcji OpenGL kierowane do biblioteki opengl32.dll. Wcześniej stosowane były tzw. sterowniki miniklienta MCD (ang. Mini-Client Driver), które część operacji kierowały do procesora karty graficznej, wykonanie reszty poleceń pozostawiając implementacji programowej biblioteki OpenGL. Oba rozwiązania powodują, że programy odwołują się wyłącznie do biblioteki opengl32.dll co uniezależnia programistę od sterowników OpenGL znajdujących się na komputerze użytkownika.
Wadą mechanizmu zastosowanego w systemach Microsoft Windows jest brak możliwości bezpośredniego (tzn. za pośrednictwem bibliotek importowych) korzystania z mechanizmów dostępnych w wersjach 1.2 OpenGL i nowszych oraz z rozszerzeń. Problem ten dotyczy w szczególności niemożliwości bezpośredniego wywoływania funkcji. Dostęp do tych funkcji realizowany jest poprzez pobieranie wskaźników do funkcji. W tym celu należy wywołać funkcję z biblioteki WGL:
LPCSTR wglGetProcAddress( LPCSTR lpszProc )
podając jako parametr lpszProc nazwę wybranej funkcji. Funkcja ta zwraca wskaźnik do wybranej funkcji odpowiedniej do bieżącego kontekstu renderingu. Jeżeli kontekst taki nie istnieje lub nazwa funkcji jest niepoprawna wglGetProcAddress zwróci wartość NULL. Szczególnie ważny jest warunek aby uzyskany wskaźnik na funkcję był używany tylko z odpowiadającym mu kontekstem renderingu. Zmiana kontekstu wymaga ponownego pobrania wskaźników na funkcje. Dotyczy to w szczególności sytuacji, gdy nowy kontekst posiada inny format bufora obrazu. Rendering w różnych trybach graficznych jest bowiem zazwyczaj obsługiwany przez odmienne funkcje sterownika ICD.
OpenGL w systemach Linux (UNIX)
Dostępność biblioteki OpenGL w systemach Linux (UNIX) to obszerny temat, przekraczający ramy niniejszego tekstu. W systemach tych stosowane są różnego rodzaju X Serwery (darmowe i komercyjne) oraz różne rodzaje sterowników OpenGL. Wśród tych ostatnich chyba największą obecnie popularnością cieszą się sterowniki DRI (ang. Direct Rendering Infrastructure) współpracujące z pakietem Mesa, udostępniające sprzętową akcelerację biblioteki OpenGL dla wielu kart graficznych. Natomiast komercyjne wersje systemu UNIX często korzystają ze specjalnych sterowników.
Różnorodność platform i sterowników powoduje, że najbezpieczniejszym i najbardziej przenośnym sposobem dostępu do funkcji rozszerzeń biblioteki OpenGL w systemach Linux (UNIX) jest pobieranie adresów tych funkcji. W tym celu należy wywołać funkcję z biblioteki GLX:
void * glXGetProcAddressARB( const GLubyte * procName )
podając jako parametr procName nazwę wybranej funkcji. W przypadku braku takiej funkcji glXGetProcAddressARB zwróci wartość NULL. Zasadnicza różnica w stosunku do funkcji wglGetProcAddress polega na tym, że zwracany wskaźnik funkcji jest niezależny od kontekstu renderingu.
Przyrostek ARB oznacza, że funkcja glXGetProcAddressARB jest rozszerzeniem biblioteki GLX. Jest to rozszerzenie ARB get proc address. Od wersji 1.4 rozszerzenie ARB get proc address stanowi integralną część specyfikacji biblioteki GLX, a funkcja glXGetProcAddressARB, po zabraniu przyrostka ARB, otrzymała nazwę glXGetProcAddress.
OpenGL w systemie MAC OS X
System MAC OS X zawiera bardzo dobre wsparcie dla biblioteki OpenGL. Kolejne wersje systemu różnią się jednak co do zakresu dostępnych bezpośrednio funkcji OpenGL oraz rozszerzeń OpenGL. Problem ten podobnie jak w przypadku poprzednio opisanych systemów najlepiej jest rozwiązać pobierając wskaźniki do odpowiednich funkcji. Realizuje to opisana w dokumentacji technicznej (Technical Q&A QA1255
http://developer.apple.com/qa/qa2001/qa1225.html i Technical Q&A QA1255
http://developer.apple.com/qa/qa2001/qa1188.html) nieoficjalna funkcja biblioteki AGL:
void * aglGetProcAddress( char * pszProc )
zwracająca wskaźnik do funkcji o nazwie podanej w parametrze pszProc. W przypadku braku takiej funkcji aglGetProcAddress zwróci wartość NULL. Zwracane wskaźniki są niezależne od kontekstu renderingu.
Program przykładowy
W przykładowym programie (plik GL ARB transpose matrix) zobaczymy jak w pełni przenośny sposób uzyskać dostęp do wybranych funkcji rozszerzenia ARB transpose matrix. Rozszerzenie to pozwala na stosowanie w programie macierzy zapisywanych w sposób stosowany w językach C/C++ tj. w układzie wierszowym. Jest to zapis macierzy odwrotny (transponowany) w stosunku do sposobu przechowywania macierzy w bibliotece OpenGL. Rozszerzenie zostało włączone do specyfikacji biblioteki OpenGL w wersji 1.3.
Na początku do programu dołączamy plik nagłówkowy glext.h:
W przypadku, gdy program kompilowany będzie w systemie Linux (UNIX)
musimy dodać plik nagłówkowy biblioteki narzędziowej GLX.
#ifndef WIN32
#define GLX_GLXEXT_LEGACY
#include <GL/glx.h>
#define wglGetProcAddress glXGetProcAddressARB
#endif
Zdefiniowanie stałej preprocesora GLX GLXEXT LEGACY udostępnia definicję funkcji glXGetProcAddressARB. Z uwagi na brak dostępu do komputera Macintosh, celowo zrezygnowano w tej części programu z obsługi systemu MAC OS X:
W wybranym miejscu programu (np. po włączeniu wszystkich plików nagłówkowych) umieszczamy definicje wskaźników na funkcje rozszerzenia GL ARB transpose matrix, z których będziemy korzystać:
PFNGLLOADTRANSPOSEMATRIXFARBPROC glLoadTransposeMatrixfARB = NULL; PFNGLMULTTRANSPOSEMATRIXFARBPROC glMultTransposeMatrixfARB = NULL;
W funkcji main, po utworzeniu kontekstu renderingu, wywołujemy funkcję ExtensionSetup, w której sprawdzamy w pierwszej kolejności obsługiwaną wersję biblioteki OpenGL. Jeżeli jest to wersja 1.3 lub nowsza na wyżej zdefiniowane wskaźniki pobieramy adresy funkcji o nazwach bez przyrostka ARB:
const char * version =( char * ) glGetString( GL_VERSION );
int major = 0, minor = 0;
if( sscanf( version, "%d.%d", & major, & minor ) != 2 )
{
printf( "Błędny format wersji OpenGL\n" );
exit( 0 );
}
if( major > 1 || minor >= 3 )
{
glLoadTransposeMatrixfARB =( PFNGLLOADTRANSPOSEMATRIXFARBPROC ) wglGetProcAddress
( "glLoadTransposeMatrixf" );
glMultTransposeMatrixfARB =( PFNGLMULTTRANSPOSEMATRIXFARBPROC ) wglGetProcAddress
( "glMultTransposeMatrixf" );
}
Jeżeli obsługiwana wersja biblioteki OpenGL jest wcześniejsza niż 1.3 sprawdzamy, czy obsługiwane jest rozszerzenie GL ARB transpose matrix i jeżeli tak, to pobieramy adres do wybranych funkcji:
if( glutExtensionSupported( "GL_ARB_transpose_matrix" ) )
{
glLoadTransposeMatrixfARB =( PFNGLLOADTRANSPOSEMATRIXFARBPROC ) wglGetProcAddress
( "glLoadTransposeMatrixfARB" );
glMultTransposeMatrixfARB =( PFNGLMULTTRANSPOSEMATRIXFARBPROC ) wglGetProcAddress
( "glMultTransposeMatrixfARB" );
}
Oczywiście negatywny wynik obu testów przerywa program.
Czytelnik zapewne zada pytanie dlaczego wykonywane są dwustopniowe testy obsługi rozszerzenia, zamiast wykonania tylko drugiego etapu i pobrania adresów do funkcji z przyrostkiem ARB. Autor proponuje takie rozwiązanie ponieważ podczas testów programu w systemie Linux Mandriva LE2005 zawierającego obsługę biblioteki OpenGL w wersji 1.4 (sterowniki DRI + biblioteka Mesa), funkcja glXGetProcAddress zwracała błędne adresy funkcji z przyrostkiem ARB.
Na koniec opiszemy działanie funkcji zawartych w rozszerzeniu GL ARB transpose matrix. Dwie funkcje służą do zmiany bieżącej macierzy:
void glLoadTransposeMatrixfARB( const GLfloat * m )
void glLoadTransposeMatrixdARB( const GLdouble * m )
na macierz wskazaną przez wskaźnik m. Dwie pozostałe funkcje mnożą bieżącą macierz przez macierz wskazaną w parametrze m:
void glMultTransposeMatrixfARB( const GLfloat * m )
void glMultTransposeMatrixdARB( const GLdouble * m )
Po włączeniu rozszerzenia GL ARB transpose matrix do specyfikacji biblioteki OpenGL powyższe funkcje utraciły przyrostki ARB:
void glLoadTransposeMatrixf( const GLfloat * m )
void glLoadTransposeMatrixd( const GLdouble * m )
void glMultTransposeMatrixf( const GLfloat * m )
void glMultTransposeMatrixd( const GLdouble * m )
W przykładowym programie funkcje (w wersji operującej na tablicach liczb GLfloat) zostały wykorzystane do modyfikacji macierzy modelowania w celu skalowania wyświetlanego na środku okna kwadratu (patrz funkcja DisplayScene).
Plik GL_ARB_transpose_matrix.cpp
#include <GL/glut.h>
#include <GL/glext.h>
#ifndef WIN32
#define GLX_GLXEXT_LEGACY
#include <GL/glx.h>
#define wglGetProcAddress glXGetProcAddressARB
#endif
#include <stdlib.h>
#include <stdio.h>
PFNGLLOADTRANSPOSEMATRIXFARBPROC glLoadTransposeMatrixfARB = NULL;
PFNGLMULTTRANSPOSEMATRIXFARBPROC glMultTransposeMatrixfARB = NULL;
enum
{
EXIT
};
GLfloat scale = 1.0;
void DisplayScene()
{
glClearColor( 1.0, 1.0, 1.0, 1.0 );
glClear( GL_COLOR_BUFFER_BIT );
glColor3f( 1.0, 0.0, 0.0 );
glMatrixMode( GL_MODELVIEW );
GLfloat id[ 16 ] = { 1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0 };
glLoadTransposeMatrixfARB( id );
GLfloat sc[ 16 ] = { scale, 0.0, 0.0, 0.0,
0.0, scale, 0.0, 0.0,
0.0, 0.0, scale, 0.0,
0.0, 0.0, 0.0, 1.0 };
glMultTransposeMatrixfARB( sc );
glRectf( - 0.5, - 0.5, 0.5, 0.5 );
glFlush();
glutSwapBuffers();
}
void Reshape( int width, int height )
{
glViewport( 0, 0, width, height );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
gluOrtho2D( - 1, 1, - 1, 1 );
DisplayScene();
}
void Keyboard( unsigned char key, int x, int y )
{
if( key == '+' )
scale += 0.01;
else
if( key == '-' && scale > 0.01 )
scale -= 0.01;
Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
}
void Menu( int value )
{
if( value == EXIT )
exit( 0 );
}
void ExtensionSetup()
{
const char * version =( char * ) glGetString( GL_VERSION );
int major = 0, minor = 0;
if( sscanf( version, "%d.%d", & major, & minor ) != 2 )
{
#ifndef WIN32
printf( "Błędny format wersji OpenGL\n" );
#else
printf( "Bledny format wersji OpenGL\n" );
#endif
exit( 0 );
}
if( major > 1 || minor >= 3 )
{
glLoadTransposeMatrixfARB =
( PFNGLLOADTRANSPOSEMATRIXFARBPROC ) wglGetProcAddress
( "glLoadTransposeMatrixf" );
glMultTransposeMatrixfARB =
( PFNGLMULTTRANSPOSEMATRIXFARBPROC ) wglGetProcAddress
( "glMultTransposeMatrixf" );
}
else
if( glutExtensionSupported( "GL_ARB_transpose_matrix" ) )
{
glLoadTransposeMatrixfARB =
( PFNGLLOADTRANSPOSEMATRIXFARBPROC ) wglGetProcAddress
( "glLoadTransposeMatrixfARB" );
glMultTransposeMatrixfARB =
( PFNGLMULTTRANSPOSEMATRIXFARBPROC ) wglGetProcAddress
( "glMultTransposeMatrixfARB" );
}
else
{
printf( "Brak rozszerzenia GL_ARB_transpose_matrix!\n" );
exit( 0 );
}
}
int main( int argc, char * argv[] )
{
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
glutInitWindowSize( 500, 500 );
glutCreateWindow( "GL_ARB_transpose_matrix" );
glutDisplayFunc( DisplayScene );
glutReshapeFunc( Reshape );
glutKeyboardFunc( Keyboard );
glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
ExtensionSetup();
glutMainLoop();
return 0;
}
Dodatkowe biblioteki
Prezentowane wyżej rozwiązanie obsługi rozszerzeń OpenGL ma jedną zasadniczą wadę - dodanie obsługi każdego nowego rozszerzenia wymaga definiowania wszystkich niezbędnych elementów. Jest to zajęcie żmudne ale na szczęście inni programiści też zauważyli ten problem i stworzyli ułatwienia w postaci dodatkowych bibliotek.
Jednym z ciekawszych projektów jest biblioteka GLEW (ang. OpenGL Extension Wrangler Library), dostępna wraz z pełnym kodem źródłowym pod adresem
http://glew.sourceforge.net/. Jest to kompleksowe rozwiązanie obsługujące praktycznie każde udokumentowane rozszerzenia biblioteki OpenGL, WGL i GLX. GLEW obsługuje także wszystkie nowe elementy OpenGL aż do wersji 2.0.
Używanie biblioteki jest bardzo proste. Po inicjalizacji realizowanej wywołaniem funkcji glewInit, dostępność wybranego rozszerzenia określają stałe z przedrostkiem GLEW (przykładowo GLEW ARB vertex program). Po sprawdzeniu dostępności wybranego rozszerzenia GLEW udostępnia wszystkie stałe i funkcje w nim zdefiniowane. Podobnie można sprawdzić czy dana implementacja obsługuje określoną wersję biblioteki OpenGL (przykładowa stała GLEW VERSION 1 3), a następnie korzystać ze wszystkich możliwości oferowanych przez daną wersję OpenGL.
Bibliotekę uzupełniają programy glewinfo kompleksowo informujący o możliwościach implementacji OpenGL dostępnej w systemie oraz visualinfo podający wykaz obsługiwanych rozszerzeń OpenGL, WGL i GLX. Dla zaawansowanych użytkowników GLEW udostępnia zestaw skryptów napisanych w języku Perl pozwalających m.in. na automatyczne generowanie kodów źródłowych na podstawie specyfikacji rozszerzenia.
Inne warte zainteresowania projekty to:
Wybór pozostawiamy Czytelnikowi, a w przykładowych programach nie będą wykorzystywane żadne dodatkowe biblioteki wspomagające używanie rozszerzeń OpenGL.
Na koniec warto dodać, że biblioteka GLUT w wersji rozprowadzanej z pakietem Mesa zawiera przydatną, ale niestety nieoficjalną, funkcję:
void * glutGetProcAddress( const char * procName )
która zwraca wskaźnik do wybranej funkcji OpenGL lub rozszerzenia oraz dodatkowo funkcji z biblioteki GLUT. Podobnie jak opisywane we wcześniejszym punkcie funkcje narzędziowe glutGetProcAddress zwraca NULL jeżeli dana funkcja nie jest dostępna.