Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: Janusz Ganczarski
Biblioteki C++

Rozszerzenia

[lekcja] Rozdział 9. Informacja o implementacji biblioteki OpenGL; wprowadzenie do rozszerzeń OpenGL; budowa specyfikacji rozszerzenia; używanie rozszerzeń na przykładzie ARB_transpose_matrix; dwa przykładowe programy.
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:
C/C++
const GLubyte * glGetString( GLenum name )
Parametr name określa rodzaj informacji zwracanej przez funkcję i przyjmuje jedną z poniższych wartości:
  • GL_VENDOR - autor implementacji OpenGL,
  • GL RENDERER - nazwa urządzenia renderującego, np. karty graficznej,
  • GL VERSION - numer wersji implementacji OpenGL w formacie: wersja.- podwersja lub wersja.podwersja.wydanie oraz opcjonalnie po poje- dynczej spacji producent biblioteki,
  • GL EXTENSIONS - wykaz obsługiwanych rozszerzeń OpenGL.
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:
C/C++
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:
C/C++
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:
C/C++
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.
Rysunek 1. Informacje o bibliotece OpenGL i GLU - Windows XP Home
Rysunek 1. Informacje o bibliotece OpenGL i GLU - Windows XP Home
Rysunek 2. Informacje o bibliotece OpenGL i GLU - Mandriva Linux LE2005
Rysunek 2. Informacje o bibliotece OpenGL i GLU - Mandriva Linux LE2005
Rysunek 3. Informacje o bibliotece OpenGL i GLU - Mesa 6.0 w systemie Windows XP Home
Rysunek 3. Informacje o bibliotece OpenGL i GLU - Mesa 6.0 w systemie Windows XP Home
Rysunek 4. Informacje o bibliotece OpenGL i GLU - Windows XP Home z kartą graficzną ATI Radeon X700
Rysunek 4. Informacje o bibliotece OpenGL i GLU - Windows XP Home z kartą graficzną ATI Radeon X700

Plik wersja opengl.cpp

C/C++
/*
(c) Janusz Ganczarski
http://www.januszg.hg.pl
JanuszG@enter.net.pl
*/

#include <GL/glut.h>
#include <stdlib.h>
#include <string.h>

// stałe do obsługi menu podręcznego

enum
{
    EXIT // wyjście
};

// funkcja rysująca napis w wybranym miejscu

void DrawString( int x, int y, char * string )
{
    // położenie napisu
    glRasterPos2i( x, y );
   
    // wyświetlenie napisu
    int len = strlen( string );
    for( int i = 0; i < len; i++ )
         glutBitmapCharacter( GLUT_BITMAP_9_BY_15, string[ i ] );
   
}

// funkcja generująca scenę 3D

void Display()
{
    // kolor tła - zawartość bufora koloru
    glClearColor( 1.0, 1.0, 1.0, 1.0 );
   
    // czyszczenie bufora koloru
    glClear( GL_COLOR_BUFFER_BIT );
   
    // kolor napisów
    glColor3f( 0.0, 0.0, 0.0 );
   
    // ciąg znaków używany przy wyświetlaniu napisów
    char string[ 2048 ];
   
    // rozmiary okna renderingu
    GLuint height = glutGet( GLUT_WINDOW_HEIGHT ) - 15;
    GLuint width = glutGet( GLUT_WINDOW_WIDTH );
   
    // autor implementacji OpenGL
    strcpy( string, "GL_VENDOR: " );
    strcat( string,( char * ) glGetString( GL_VENDOR ) );
    DrawString( 0, height, string );
    height -= 16;
   
    // nazwa urządzenia renderującego
    strcpy( string, "GL_RENDERER: " );
    strcat( string,( char * ) glGetString( GL_RENDERER ) );
    DrawString( 0, height, string );
    height -= 16;
   
    // numer wersji implementacji OpenGL
    strcpy( string, "GL_VERSION: " );
    strcat( string,( char * ) glGetString( GL_VERSION ) );
    DrawString( 0, height, string );
    height -= 16;
   
    // lista obsługiwanych rozszerzeń OpenGL
    const GLubyte * extstr = glGetString( GL_EXTENSIONS );
    strcpy( string, "GL_EXTENSIONS: " );
    int extpos = 0;
    int extlen = strlen(( char * ) extstr );
    while( extpos < extlen )
    {
        // selekcja nazwy rozszerzenia
        int pos = extpos;
        while( extstr[ pos ] != '\0' && extstr[ pos ] != ' ' )
             pos++;
       
        // dodanie rozszerzenia do bieżącego ciągu znaków
        if( 9 *( pos - extpos + strlen( string ) ) < width )
        {
            strncat( string,( char * ) & extstr[ extpos ], pos - extpos + 1 );
            extpos = pos + 1;
        }
       
        // dodanie rozszerzenia do następnego ciągu znaków
        else
        {
            DrawString( 0, height, string );
            height -= 16;
            strcpy( string, "  " );
            strncat( string,( char * ) & extstr[ extpos ], pos - extpos + 1 );
            extpos = pos + 1;
        }
       
        // wyświetlenie ostatniego ciągu znaków
        if( extpos == extlen )
        {
            DrawString( 0, height, string );
            height -= 16;
        }
    }
   
    // numer wersji implementacji GLU
    strcpy( string, "GLU_VERSION: " );
    strcat( string,( char * ) gluGetString( GLU_VERSION ) );
    DrawString( 0, height, string );
    height -= 16;
   
    // lista obsługiwanych rozszerzeń GLU
    extstr = gluGetString( GLU_EXTENSIONS );
    strcpy( string, "GLU_EXTENSIONS: " );
    extpos = 0;
    extlen = strlen(( char * ) extstr );
    while( extpos < extlen )
    {
        // selekcja nazwy rozszerzenia
        int pos = extpos;
        while( extstr[ pos ] != '\0' && extstr[ pos ] != ' ' )
             pos++;
       
        // dodanie rozszerzenia do bieżącego ciągu znaków
        if( 9 *( pos - extpos + strlen( string ) ) < width )
        {
            strncat( string,( char * ) & extstr[ extpos ], pos - extpos + 1 );
            extpos = pos + 1;
        }
        else
       
        // dodanie rozszerzenia do następnego ciągu znaków
        {
            DrawString( 0, height, string );
            height -= 16;
            strcpy( string, "  " );
            strncat( string,( char * ) & extstr[ extpos ], pos - extpos + 1 );
            extpos = pos + 1;
        }
       
        // wyświetlenie ostatniego ciągu znaków
        if( extpos >= extlen )
        {
            DrawString( 0, height, string );
            height -= 16;
        }
    }
   
    // skierowanie poleceń do wykonania
    glFlush();
   
    // zamiana buforów koloru
    glutSwapBuffers();
}

// zmiana wielkości okna

void Reshape( int width, int height )
{
    // obszar renderingu - całe okno
    glViewport( 0, 0, width, height );
   
    // wybór macierzy rzutowania
    glMatrixMode( GL_PROJECTION );
   
    // macierz rzutowania = macierz jednostkowa
    glLoadIdentity();
   
    // rzutowanie prostokątne
    gluOrtho2D( 0, width - 1, 0, height - 1 );
   
    // generowanie sceny 3D
    Display();
}

// obsługa menu podręcznego

void Menu( int value )
{
    if( value == EXIT )
         exit( 0 );
   
}

int main( int argc, char * argv[] )
{
    // inicjalizacja biblioteki GLUT
    glutInit( & argc, argv );
   
    // inicjalizacja biblioteki GLUT
    glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
   
    // rozmiary głównego okna programu
    glutInitWindowSize( 600, 600 );
   
    // rozmiary głównego okna programu
    glutCreateWindow( "Wersja OpenGL" );
   
    // dołączenie funkcji generującej scenę 3D
    glutDisplayFunc( Display );
   
    // dołączenie funkcji wywoływanej przy zmianie rozmiaru okna
    glutReshapeFunc( Reshape );
   
    // menu główne
    glutCreateMenu( Menu );
    #ifdef WIN32
   
    glutAddMenuEntry( "Wyjście", EXIT );
    #else
   
    glutAddMenuEntry( "Wyjscie", EXIT );
    #endif
   
    // określenie przycisku myszki obsługującego menu podręczne
    glutAttachMenu( GLUT_RIGHT_BUTTON );
   
    // wprowadzenie programu do obsługi pętli komunikatów
    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:
C/C++
#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:
C/C++
#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:
C/C++
#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:
C/C++
#ifndef GL_SUNX_constant_data
#define GL_SUNX_constant_data 1
#ifdef GL_GLEXT_PROTOTYPES
GLAPI void APIENTRY glFinishTextureSUNX( void );
#endif /* GL_GLEXT_PROTOTYPES */
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:
C/C++
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:
C/C++
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.
C/C++
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:
C/C++
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:
C/C++
#include <GL/glext.h>
W przypadku, gdy program kompilowany będzie w systemie Linux (UNIX)
musimy dodać plik nagłówkowy biblioteki narzędziowej GLX.
C/C++
#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ć:
C/C++
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:
C/C++
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:
C/C++
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:
C/C++
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:
C/C++
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:
C/C++
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

C/C++
/*
(c) Janusz Ganczarski
http://www.januszg.hg.pl
JanuszG@enter.net.pl
*/

#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>

// wskaźniki na funkcje: glLoadTransposeMatrixfARB i glMultTransposeMatrixfARB

PFNGLLOADTRANSPOSEMATRIXFARBPROC glLoadTransposeMatrixfARB = NULL;
PFNGLMULTTRANSPOSEMATRIXFARBPROC glMultTransposeMatrixfARB = NULL;

// stałe do obsługi menu podręcznego

enum
{
    EXIT // wyjście
};

// współczynnik skalowania

GLfloat scale = 1.0;

// funkcja generująca scenę 3D

void DisplayScene()
{
    // kolor tła - zawartość bufora koloru
    glClearColor( 1.0, 1.0, 1.0, 1.0 );
   
    // czyszczenie bufora koloru
    glClear( GL_COLOR_BUFFER_BIT );
   
    // kolor napisów
    glColor3f( 1.0, 0.0, 0.0 );
   
    // wybór macierzy modelowania
    glMatrixMode( GL_MODELVIEW );
   
    // macierz jednostkowa
    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 };
   
    // macierz modelowania = macierz jednostkowa
    glLoadTransposeMatrixfARB( id );
   
    // macierz skalowania
    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 };
   
    // skalowanie obiektu
    glMultTransposeMatrixfARB( sc );
   
    // prostokąt
    glRectf( - 0.5, - 0.5, 0.5, 0.5 );
   
    // skierowanie poleceń do wykonania
    glFlush();
   
    // zamiana buforów koloru
    glutSwapBuffers();
}

// zmiana wielkości okna

void Reshape( int width, int height )
{
    // obszar renderingu - całe okno
    glViewport( 0, 0, width, height );
   
    // wybór macierzy rzutowania
    glMatrixMode( GL_PROJECTION );
   
    // macierz rzutowania = macierz jednostkowa
    glLoadIdentity();
   
    // rzutowanie prostokątne
    gluOrtho2D( - 1, 1, - 1, 1 );
   
    // generowanie sceny 3D
    DisplayScene();
}

// obsługa klawiatury

void Keyboard( unsigned char key, int x, int y )
{
    // klawisz +
    if( key == '+' )
         scale += 0.01;
    else
   
    // klawisz -
    if( key == '-' && scale > 0.01 )
         scale -= 0.01;
   
    // odrysowanie okna
    Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
}

// obsługa menu podręcznego

void Menu( int value )
{
    if( value == EXIT )
         exit( 0 );
   
}

// sprawdzenie i przygotowanie obsługi wybranych rozszerzeń

void ExtensionSetup()
{
    // pobranie numeru wersji biblioteki OpenGL - obsługa rozszerzenia
    // GL_ARB_transpose_matrix została dodana do wersji OpenGL 1.3
    const char * version =( char * ) glGetString( GL_VERSION );
   
    // odczyt wersji OpenGL
    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 );
    }
   
    // sprawdzenie czy jest co najmniej wersja 1.3
    if( major > 1 || minor >= 3 )
    {
        // pobranie wskaźników wybranych funkcji OpenGL 1.3
        glLoadTransposeMatrixfARB =
        ( PFNGLLOADTRANSPOSEMATRIXFARBPROC ) wglGetProcAddress
        ( "glLoadTransposeMatrixf" );
        glMultTransposeMatrixfARB =
        ( PFNGLMULTTRANSPOSEMATRIXFARBPROC ) wglGetProcAddress
        ( "glMultTransposeMatrixf" );
    }
    else
   
    // sprawdzenie czy jest obsługiwane rozszerzenie GL_ARB_transpose_matrix
    if( glutExtensionSupported( "GL_ARB_transpose_matrix" ) )
    {
        // pobranie wskaźników wybranych funkcji rozszerzenia 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[] )
{
    // inicjalizacja biblioteki GLUT
    glutInit( & argc, argv );
   
    // inicjalizacja biblioteki GLUT
    glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
   
    // rozmiary głównego okna programu
    glutInitWindowSize( 500, 500 );
   
    // rozmiary głównego okna programu
    glutCreateWindow( "GL_ARB_transpose_matrix" );
   
    // dołączenie funkcji generującej scenę 3D
    glutDisplayFunc( DisplayScene );
   
    // dołączenie funkcji wywoływanej przy zmianie rozmiaru okna
    glutReshapeFunc( Reshape );
   
    // dołączenie funkcji obsługi klawiatury
    glutKeyboardFunc( Keyboard );
   
    // menu główne
    glutCreateMenu( Menu );
    #ifdef WIN32
   
    glutAddMenuEntry( "Wyjście", EXIT );
    #else
   
    glutAddMenuEntry( "Wyjscie", EXIT );
    #endif
   
    // określenie przycisku myszki obsługującego menu podręczne
    glutAttachMenu( GLUT_RIGHT_BUTTON );
   
    // sprawdzenie i przygotowanie obsługi wybranych rozszerzeń
    ExtensionSetup();
   
    // wprowadzenie programu do obsługi pętli komunikatów
    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ę:
C/C++
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.
Poprzedni dokument Następny dokument
Kolory i cieniowanie Mapy bitowe