Obecne karty graficzne posiadają bardzo duże ilości pamięci. Jedynie stosunkowo niewielka część z tej pamięci przeznaczona jest na bufor ramki. Pozostałą pamięć mogą wykorzystywać tekstury (mechanizm obiektów tekstur) lub wprowadzone w wersji 1.5 biblioteki OpenGL obiekty buforowe wierzchołków VBO (ang. vertex buffer object), wcześniej dostępne w rozszerzeniu ARB vertex buffer object. W wersji 2.1 biblioteki dodano obsługę obiektów buforowych pikseli PBO (ang. pixel buffer object), które wcześniej obsługiwały rozszerzenia ARB pixel buffer object i EXT pixel buffer - object. Obiekty buforowe PBO i VBO korzystają z tego samego interfejsu, funkcjonalnością zbliżonego do mechanizmów obiektów tekstur.
Generowanie identyfikatorów obiektów
Utworzenie wskazanej w parametrze n ilości unikatowych identyfikatorów (nazw) obiektów buforowych realizuje funkcja:
void glGenBuffers( GLsizei n, GLuint * buffers )
Numery identyfikatorów zwracane sa w tablicy wskazywanej w parametrze buffers. Oczywiście program musi zapewnić odpowiednią ilość miejsca w tablicy. Identyfikator o wartości zero nie jest związany z żadnym obiektem buforowym.
Sprawdzenie czy wskazany identyfikator jest identyfikatorem obiektu buforowego umożliwia funkcja:
GLboolean glIsBuffer( GLuint buffer )
która zwraca stosowne wartości logiczne (
GL_TRUE lub
GL_FALSE).
Dowiązanie obiektów buforowych
Utworzony identyfikator trzeba dowiązać do obiektu buforowego wywołując funkcję:
void glBindBuffer( GLenum target, GLuint buffer )
której parametr buffer zawiera unikatowy identyfikator obiektu, a parametr target określa rodzaj obiektu buforowanego. Dostępne są następujące rodzaje obiektów buforowych:
Pierwsze dwa rodzaje obiektów buforowych należą do grupy obiektów buforowych wierzchołków (VBO), dwa pozostałe to obiekty buforowe pikseli (PBO).
Pierwsze wywołanie funkcji glBindBuffer tworzy obiekt buforowy, a następne wywołania służą do przełączania się pomiędzy wybranymi obiektami. Opisane dalej operacje na obiektach buforowych wykonywane są zawsze na bieżącym obiekcie, wybranym przy użyciu funkcji glBindBuffer.
Usuwanie obiektów
Usuwanie wybranej grupy obiektów buforowych realizuje funkcja:
void glDeleteBuffers( GLsizei n, const GLuint * buffers )
Parametr n określa ilość usuwanych obiektów buforowych, których identyfikatowy zawiera tablica buffers.
Ładowanie danych do obiektu
Ładowanie danych do obiektu buforowego można zrealizować na dwa sposoby. Pierwszy polega na skorzystaniu z tradycyjnego modelu aplikacji OpenGL, tj. kopiowaniu danych do obiektu bufora z tablicy (lub innej struktury danych) zawartej w pamięci operacyjnej komputera. Taki model ładowania danych wykorzystują obiekty tekstur. Druga metoda korzysta z tzw. odwzorowania obiektu buforowego (ang. mapping buffer object) i pozwala na bezpośrednie operowanie na pamięci obiektu.
Zauważmy, że pierwsza z wymienionych metod wymaga zarezerwowania odpowiedniej ilości pamięci operacyjnej komputera, choć może to być alokacja tymczasowa (dynamiczna). Drugi sposób nie wymaga przydzielania dodatkowych zasobów systemowych.
Niezależnie od przyjętej metody ładowanie danych do obiektu buforowego wymaga wywołania funkcji:
void glBufferData( GLenum target, GLsizeiptr size, const GLvoid * data, GLenum usage )
Parametr target określa rodzaj obiektu buforowego, którego dane tworzymy. Możliwe są cztery opisane wcześniej wartości:
GL_ARRAY_BUFFER, GL - ELEMENT ARRAY BUFFER,
GL_PIXEL_UNPACK_BUFFER i
GL_PIXEL_PACK_BUFFER.
Drugi parametr size wskazuje rozmiar danych bufora obiektu buforowego. Wielkość ta jest określana w BMU - podstawowe jednostkach maszynowych (ang. basic machine units), czyli w bajtach. Jedynym sposobem na zmianę wielkości bufora jest usunięcie obiektu i ponowne jego utworzenie z żądanym rozmiarem.
Kolejny parametr data zawiera wskaźnik na dane ładowane do obiektu buforowego. Podanie wartości różnej od NULL powoduje skopiowanie danych z bufora zawartego w pamięci operacyjnego do bufora zawartego w pamięci karty graficznej. Natomiast w przypadku podania wartości NULL pamięć bufora obiektu buforowego jest jedynie rezerwowana, a jej zawartość jest nieokreślona. Możliwość tę wykorzystujemy w szczególności w przypadku, gdy zamierzamy bezpośrednio operować na pamięci obiektu buforowego.
Ostatni parametr usage zawiera wskazówkę dla biblioteki OpenGL dotyczącą przewidywanej metody dostępu do danych zawartych w obiekcie buforowym. Możliwe są następujące wartości:
W przypadku braku możliwości przydzielenia wymaganej ilości pamięci funkcja glBufferData generuje błąd
GL_OUT_OF_MEMORY.
Zmianę całości lub części danych zawartych w obiekcie buforowym umożliwia funkcja:
void glBufferSubData( GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid * data )
Parametr target określa oczywiście rodzaj obiektu buforowego i przyjmuje jedną z czterech znanych już wartości. Zakres modyfikowanych danych określają parametry offset i size, gdzie offset jest numerem pierwszego zmienianego elementu danych bufora obiektu (numeracja elementów zaczyna się od zera i jest prowadzona w bajtach), a size to ilość modyfikowanych elementów bufora. Nowe dane zawarte są w tablicy wskazywanej w parametrze data.
Wspomniana wcześniej metoda odwzorowania obiektu buforowego wymaga uzyskania wskaźnika do danych obiektu buforowego. Wymaga to wywołania funkcji:
GLvoid * glMapBuffer( GLenum target, GLenum access )
której parametr target określa rodzaj obiektu buforowego i przyjmuje znane już wartości:
GL_ARRAY_BUFFER,
GL_ELEMENT_ARRAY_BUFFER,
GL_PIXEL_UNPACK_BUFFER i
GL_PIXEL_PACK_BUFFER.
Drugi parametr access wskazuje do jakich operacji będzie wykorzystany pobierany wskaźnik na dane obiektu buforowego:
Wykorzystanie wskaźnika niezgodnie z planowanym przeznaczeniem nie powoduje wprawdzie zgłoszenia błędu przez bibliotekę OpenGL, ale efekt działania jest nieokreślony.
Jeżeli odwzorowywany obiekt jest już w tym trybie funkcja glMapBuffer zwraca wartość NULL oraz dodatkowo generowany jest błąd
GL_INVALID - OPERATION.
Gdy obiekt buforowy znajduje się w trybie nie można na min wykonywać innych operacji niż związanych z pobieraniem (lub ładowaniem) danych. W szczególności wywołanie funkcji glBufferSubData spowoduje wystąpienie błędu
GL_INVALID_OPERATION. Efekt działania innych operacji związanych z obiektem buforowym jest nieokreślony. Zezwolenie na takie zachowanie obiektów buforowych specyfikacji biblioteki OpenGL tłumaczy możliwością uzyskania wyższej wydajności.
Po zakończeniu operacji w trybie odwzorowania trzeba zwolnić pobrany wskaźnik do danych obiektu buforowego wywołując funkcję:
GLboolean glUnmapBuffer( GLenum target )
której parametr target określa oczywiście rodzaj obiektu buforowego.
W przypadku poprawnego wyjścia obiektu buforowego z trybu odwzorowania funkcja glUnmapBuffer zwraca wartość
GL_TRUE. Wartość
GL_FALSE zostanie zwrócona, gdy obiekt nie znajduje się w trybie odwzorowania oraz w każdym przypadku wystąpienia zdarzenia wpływającego na zmianę stanu pamięci karty graficznej. Takim zdarzeniami mogą być w szczególności: zmiana rozdzielczości ekranu, przejście karty graficznej w tryb oszczędzania energii. Wystąpienie takich sytuacji wymaga ponownego załadowania danych do pamięci bufora obiektu.
Pobieranie właściwości obiektu
Pobranie właściwości bieżącego obiektu buforowego umożliwia funkcja:
void glGetBufferParameteriv( GLenum target, GLenum pname, GLint * params )
Parametr target określa rodzaj obiektu buforowego i przyjmuje jedną z dobrze znanych wartości:
GL_ARRAY_BUFFER,
GL_ELEMENT_ARRAY_BUFFER,
GL_PIXEL_UNPACK_BUFFER i
GL_PIXEL_PACK_BUFFER.
Drugi parametr pname zawiera rodzaj pobieranej właściwości obiektu buforowego, który zostaje zwrócony w parametrze params. Możliwe są następujące wartości:
Jeżeli obiekt buforowy znajduje się w trybie odwzorowania możliwe jest pobranie wskaźnika na dane obiektu. Wymaga to wywołania funkcji:
void glGetBufferPointerv( GLenum target, GLenum pname, GLvoid ** params )
której parametr target określa rodzaj obiektu buforowego (cztery znane wartości:
GL_ARRAY_BUFFER,
GL_ELEMENT_ARRAY_BUFFER,
GL_PIXEL_UNPACK - BUFFER i
GL_PIXEL_PACK_BUFFER), a drugi parametr pname może przyjąć
Tabela 1: Zestawienie właściwości obiektów buforowych
jedynie wartość
GL_BUFFER_MAP_POINTER, oznaczającą właśnie pobieranie wskaźnika na dane obiektu. Wskaźnik jest zwracany w ostatnim parametrze params.
W przypadku, gdy wybrany obiekt buforowy nie jest w trybie odwzorowania w parametrze params zostanie zwrócona wartość NULL.
Zestawienie wszystkich właściwości obiektów buforowych wraz z dopuszczalnymi wartościami oraz wartościami początkowymi przedstawia tabela 1.
Pobieranie danych obiektu
Biblioteka OpenGL umożliwia pobranie wybranych danych zawartych w obiekcie buforowym przy użyciu funkcji:
void glGetBufferSubData( GLenum target, GLintptr offset, GLsizeiptr size, GLvoid * data )
której parametr target określa rodzaj obiektu buforowego, a parametry offset i size wyznaczają odpowiednio pierwszy pobierany element oraz ilość kopiowanych elementów. Numeracja elementów obiektu buforowego zaczyna się od zera i jest wyznaczana w bajtach. Bufor, do którego kopiowane są wybrane elementy, wskazywany jest w parametrze data.
Obiekty buforowe tablic wierzchołków
Dane przechowywane w obiektach buforowych tablic wierzchołków są zgodne z formatem używanym w tablicach wierzchołków. Umożliwia to bezpośrednie korzystanie z tablic wierzchołków przy użyciu funkcji: glArrayElement, glDrawArrays, glMultiDrawArrays, glDrawElements, glDrawRangeElements i glMultiDrawElements. Jedyna różnica polega na sposobie dostarczenia danych tablic wierzchołków do funkcji: glVertexPointer, glNormalPointer, glColorPointer, glSecondaryColorPointer, glIndexPointer, glEdgeFlagPointer, glFogCoordPointer i glTexCoordPointer. Zamiast wskaźnika na dane tablicy (parametr pointer powyższych funkcji) należy podać położenie początku danych w bieżącym obiekcie buforowym tablicy wierzchołków. W praktyce jeden obiekt buforowy tablic wierzchołków może zatem służyć do przechowywania danych wielu tablic wierzchołków.
Obiekty buforowe indeksowych tablic wierzchołków
Obiekty buforowe indeksowych tablic wierzchołków są bezpośrednio przystosowane do współpracy z funkcjami: glDrawElements, glDrawRangeElements i glMultiDrawElements obsługującymi indeksowe tablice wierzchołków. Jedyna różnica polega na sposobie dostarczenia danych tablicy indeksów. Zamiast wskaźnika na dane tablicy z indeksami (parametr indices dwóch pierwszych funkcji) podaje się położenie początku danych w bieżącym obiekcie buforowym indeksowej tablicy wierzchołków. W przypadku funkcji glMultiDrawElements parametr indices zawiera adresy położenia danych kolejnych tablic indeksów w bieżącym obiekcie buforowym.
Obiekty buforowe odczytu (rozpakowania) danych pikseli
Obiekty buforowe odczytu danych pikseli można wykorzystać jako źródło danych pikseli dla następujących funkcji biblioteki OpenGL: glBitmap, glColorSubTable, glColorTable, glCompressedTexImage1D, glCompressedTexImage2D, glCompressedTexImage3D, glCompressedTexSubImage1D, glCompressedTexSubImage2D, glCompressedTexSubImage3D, glConvolutionFilter1D, glConvolutionFilter2D, glDrawPixels, glPixelMapfv, glPixelMapuiv, glPixelMapusv, glPolygonStipple, glSeparableFilter2D, glTexImage1D, glTexImage2D, glTexImage3D, glTexSubImage1D, glTexSubImage2D i glTexSubImage3D.
W parametrach powyższych funkcji wskazujących źródło danych pikseli zamiast adresu tablicy z danymi wskazujemy położenie danych w obiekcie buforowym.
Obiekty buforowe zapisu (spakowania) danych pikseli
Obiekty buforowe zapisu danych pikseli można wykorzystać jako bufor danych pikseli dla następujących funkcji biblioteki OpenGL: glGetCompressedTexImage, glGetConvolutionFilter, glGetHistogram, glGetMinmax, glGetPixelMapfv, glGetPixelMapuiv, glGetPixelMapusv, glGetPolygonStipple, glGetSeparableFilter, glGetTexImage i glReadPixels.
W parametrach powyższych funkcji wskazujących tablicę na dane pikseli zamiast adresu tablicy na dane wskazujemy położenie danych w obiekcie buforowym.
Programy przykładowe
Pierwszy program przykładowy (plik vbo.cpp) jest kolejnym rozwinięciem programu służącego wcześniej do testów list wyświetlania oraz tablic wierzchołków. W porównaniu do poprzednika trzykrotnie zwiększono ilość wyświetlanych prymitywów. Zastosowanie obiektów buforowych wyraźnie zwiększyło szybkość wyświetlania prymitywów, przy czym największy wzrost szybkości dotyczy wyświetlania punktów (23%), nieco mniej zyskują odcinki (8%) i trójkąty (6%). Pamiętajmy jednak, że program faktycznie testuje jedynie szybkość renderingu, a VBO pozwala także na optymalizację zarządzania pamięcią, co umożliwia jeszcze większe przyspieszenia działania programów.
W programie zastosowano obie opisane wyżej metody ładowania danych do obiektu buforowego, jednak nie został wyeliminowany bufor zawarty w pamięci operacyjnej z uwagi na konieczność jego obecności do porównawczego testu tablic wierzchołków. Dane tablicy ze składowymi kolorów wierzchołków prymitywów i tablicy ze współrzędnymi tych wierzchołków przechowywane są w dwóch odrębnych obiektach buforowych, choć możliwe jest także wykorzystanie jednego obiektu do przechowywania danych obu tablic.
Wyniki wszystkich testów dostępnych w programie przedstawiono na rysunkach 1 - 6.
Plik vbo.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 <string.h>
#include <stdio.h>
#include <time.h>
#include "colors.h"
PFNGLGENBUFFERSPROC glGenBuffers = NULL;
PFNGLBINDBUFFERPROC glBindBuffer = NULL;
PFNGLBUFFERDATAPROC glBufferData = NULL;
PFNGLMAPBUFFERPROC glMapBuffer = NULL;
PFNGLUNMAPBUFFERPROC glUnmapBuffer = NULL;
enum
{
POINTS_VBO,
POINTS_VA,
LINES_VBO,
LINES_VA,
TRIANGLES_VBO,
TRIANGLES_VA,
FULL_WINDOW,
ASPECT_1_1,
EXIT
};
int aspect = FULL_WINDOW;
#ifdef near
#undef near
#endif
#ifdef far
#undef far
#endif
const GLdouble left = - 2.0;
const GLdouble right = 2.0;
const GLdouble bottom = - 2.0;
const GLdouble top = 2.0;
const GLdouble near = 3.0;
const GLdouble far = 7.0;
int test = POINTS_VA;
GLuint vbo_id[ 2 ];
const int size = 90000;
GLfloat vertex_xyz[ 3 * size ];
GLfloat color_rgb[ 3 * size ];
int frames = 0;
long start_time = 0;
char time_string[ 100 ] = "FPS:";
void DrawString( GLfloat x, GLfloat y, char * string )
{
glRasterPos2f( x, y );
int len = strlen( string );
for( int i = 0; i < len; i++ )
glutBitmapCharacter( GLUT_BITMAP_9_BY_15, string[ i ] );
}
void DisplayScene()
{
if( !frames++ )
start_time = clock();
glClearColor( 1.0, 1.0, 1.0, 1.0 );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glTranslatef( 0, 0, -( near + far ) / 2 );
glEnable( GL_DEPTH_TEST );
switch( test )
{
case POINTS_VBO:
case LINES_VBO:
case TRIANGLES_VBO:
glBindBuffer( GL_ARRAY_BUFFER, vbo_id[ 0 ] );
glVertexPointer( 3, GL_FLOAT, 0, 0 );
glBindBuffer( GL_ARRAY_BUFFER, vbo_id[ 1 ] );
glColorPointer( 3, GL_FLOAT, 0, 0 );
glEnableClientState( GL_VERTEX_ARRAY );
glEnableClientState( GL_COLOR_ARRAY );
if( test == POINTS_VBO )
glDrawArrays( GL_POINTS, 0, size );
else
if( test == LINES_VBO )
glDrawArrays( GL_LINES, 0, size );
else
glDrawArrays( GL_TRIANGLES, 0, size );
glDisableClientState( GL_COLOR_ARRAY );
glDisableClientState( GL_VERTEX_ARRAY );
glBindBuffer( GL_ARRAY_BUFFER, 0 );
break;
case POINTS_VA:
case LINES_VA:
case TRIANGLES_VA:
glVertexPointer( 3, GL_FLOAT, 0, vertex_xyz );
glColorPointer( 3, GL_FLOAT, 0, color_rgb );
glEnableClientState( GL_VERTEX_ARRAY );
glEnableClientState( GL_COLOR_ARRAY );
if( test == POINTS_VA )
glDrawArrays( GL_POINTS, 0, size );
else
if( test == LINES_VA )
glDrawArrays( GL_LINES, 0, size );
else
glDrawArrays( GL_TRIANGLES, 0, size );
glDisableClientState( GL_VERTEX_ARRAY );
glDisableClientState( GL_COLOR_ARRAY );
break;
};
glLoadIdentity();
glTranslatef( 0, 0, - near );
glColor3fv( Black );
if( frames == 100 )
{
frames = 0;
sprintf( time_string, "FPS: %i",( int )( 100 * CLOCKS_PER_SEC /( float )( clock() - start_time ) ) );
}
DrawString( left, bottom, time_string );
glFlush();
glutSwapBuffers();
}
void Reshape( int width, int height )
{
glViewport( 0, 0, width, height );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
if( aspect == ASPECT_1_1 )
{
if( width < height && width > 0 )
glFrustum( left, right, bottom * height / width, top * height / width, near, far );
else
if( width >= height && height > 0 )
glFrustum( left * width / height, right * width / height, bottom, top, near, far );
}
else
glFrustum( left, right, bottom, top, near, far );
DisplayScene();
}
void Menu( int value )
{
switch( value )
{
case POINTS_VBO:
case POINTS_VA:
case LINES_VBO:
case LINES_VA:
case TRIANGLES_VBO:
case TRIANGLES_VA:
test = value;
DisplayScene();
break;
case FULL_WINDOW:
aspect = FULL_WINDOW;
Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
break;
case ASPECT_1_1:
aspect = ASPECT_1_1;
Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
break;
case EXIT:
exit( 0 );
}
}
void GenerateVBO()
{
srand( time( NULL ) );
for( int i = 0; i < size; i++ )
{
vertex_xyz[ 3 * i + 0 ] =( rand() /( float ) RAND_MAX ) * 4 - 2;
vertex_xyz[ 3 * i + 1 ] =( rand() /( float ) RAND_MAX ) * 4 - 2;
vertex_xyz[ 3 * i + 2 ] =( rand() /( float ) RAND_MAX ) * 4 - 2;
color_rgb[ 3 * i + 0 ] =( rand() /( float ) RAND_MAX );
color_rgb[ 3 * i + 1 ] =( rand() /( float ) RAND_MAX );
color_rgb[ 3 * i + 2 ] =( rand() /( float ) RAND_MAX );
}
glGenBuffers( 2, vbo_id );
glBindBuffer( GL_ARRAY_BUFFER, vbo_id[ 0 ] );
glBufferData( GL_ARRAY_BUFFER, sizeof( GLfloat ) * size * 3, vertex_xyz, GL_STATIC_DRAW );
glBindBuffer( GL_ARRAY_BUFFER, vbo_id[ 1 ] );
glBufferData( GL_ARRAY_BUFFER, sizeof( GLfloat ) * size * 3, NULL, GL_STATIC_DRAW );
GLvoid * buf = glMapBuffer( GL_ARRAY_BUFFER, GL_WRITE_ONLY );
memcpy( buf, color_rgb, sizeof( GLfloat ) * size * 3 );
if( glUnmapBuffer( GL_ARRAY_BUFFER ) == GL_FALSE )
{
printf( "Niepoprawne odwzorowanie obiektu buforowego\n" );
exit( 0 );
}
glBindBuffer( GL_ARRAY_BUFFER, 0 );
}
void ExtensionSetup()
{
const char * version =( char * ) glGetString( GL_VERSION );
int major = 0, minor = 0;
if( sscanf( version, "%d.%d", & major, & minor ) != 2 )
{
#ifdef WIN32
printf( "Błędny format wersji OpenGL\n" );
#else
printf( "Bledny format wersji OpenGL\n" );
#endif
exit( 0 );
}
if( major > 1 || minor >= 5 )
{
glGenBuffers =( PFNGLGENBUFFERSPROC ) wglGetProcAddress( "glGenBuffers" );
glBindBuffer =( PFNGLBINDBUFFERPROC ) wglGetProcAddress( "glBindBuffer" );
glBufferData =( PFNGLBUFFERDATAPROC ) wglGetProcAddress( "glBufferData" );
glMapBuffer =( PFNGLMAPBUFFERPROC ) wglGetProcAddress( "glMapBuffer" );
glUnmapBuffer =( PFNGLUNMAPBUFFERPROC ) wglGetProcAddress( "glUnmapBuffer" );
}
else
if( glutExtensionSupported( "GL_ARB_vertex_buffer_object" ) )
{
glGenBuffers =( PFNGLGENBUFFERSPROC ) wglGetProcAddress( "glGenBuffersARB" );
glBindBuffer =( PFNGLBINDBUFFERPROC ) wglGetProcAddress( "glBindBufferARB" );
glBufferData =( PFNGLBUFFERDATAPROC ) wglGetProcAddress( "glBufferDataARB" );
glMapBuffer =( PFNGLMAPBUFFERPROC ) wglGetProcAddress( "glMapBufferARB" );
glUnmapBuffer =( PFNGLUNMAPBUFFERPROC ) wglGetProcAddress( "glUnmapBufferARB" );
}
else
{
printf( "Brak rozszerzenia ARB_vertex_buffer_object!\n" );
exit( 0 );
}
}
int main( int argc, char * argv[] )
{
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH );
glutInitWindowSize( 500, 500 );
glutCreateWindow( "VBO" );
glutDisplayFunc( DisplayScene );
glutReshapeFunc( Reshape );
int MenuTest = glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Punkty z VBO", POINTS_VBO );
glutAddMenuEntry( "Punkty z tablicami wierzchołków", POINTS_VA );
glutAddMenuEntry( "Odcinki z VBO", LINES_VBO );
glutAddMenuEntry( "Odcinki z tablicami wierzchołków", LINES_VA );
glutAddMenuEntry( "Trójkąty z VBO", TRIANGLES_VBO );
glutAddMenuEntry( "Trójkąty z tablicami wierzchołków", TRIANGLES_VA );
#else
glutAddMenuEntry( "Punkty z VBO", POINTS_VBO );
glutAddMenuEntry( "Punkty z tablicami wierzcholkow", POINTS_VA );
glutAddMenuEntry( "Odcinki z VBO", LINES_VBO );
glutAddMenuEntry( "Odcinki z tablicami wierzcholkow", LINES_VA );
glutAddMenuEntry( "Trojkaty z VBO", TRIANGLES_VBO );
glutAddMenuEntry( "Trojkaty z tablicami wierzcholkow", TRIANGLES_VA );
#endif
int MenuAspect = glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Aspekt obrazu - całe okno", FULL_WINDOW );
#else
glutAddMenuEntry( "Aspekt obrazu - cale okno", FULL_WINDOW );
#endif
glutAddMenuEntry( "Aspekt obrazu 1:1", ASPECT_1_1 );
glutCreateMenu( Menu );
glutAddSubMenu( "Test", MenuTest );
glutAddSubMenu( "Aspekt obrazu", MenuAspect );
#ifdef WIN32
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
ExtensionSetup();
GenerateVBO();
glutIdleFunc( DisplayScene );
glutMainLoop();
return 0;
}
Drugi przykładowy program (plik pbo.cpp) testuje obiekty buforowe pikseli przy renderingu tekstur. Wyniki uzyskane na komputerze Autora nie wskazują na przewagę szybkości w renderingu tekstur przy pomocy obiektów buforowych, ale też nie odnotowano spadku tej szybkości. Prawdopodobnie użycie większej ilości tekstur wykazałoby przewagę obiektów buforowych, która ma bezpośredni związek z większą przepustowością pamięci karty graficznej w stosunku do przepustowości pamięci operacyjnej komputera.
Przykładowy efekt działania programu przedstawiono na rysunku 7.
Plik pbo.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 <string.h>
#include <stdio.h>
#include <time.h>
#include "targa.h"
#include "colors.h"
PFNGLGENBUFFERSPROC glGenBuffers = NULL;
PFNGLBINDBUFFERPROC glBindBuffer = NULL;
PFNGLBUFFERDATAPROC glBufferData = NULL;
PFNGLGETBUFFERPARAMETERIVPROC glGetBufferParameteriv = NULL;
enum
{
TEST_PBO,
TEST_WPBO,
EXIT
};
int test = TEST_WPBO;
GLuint pbo_id;
GLuint texture_id;
GLsizei width;
GLsizei height;
GLenum format;
GLenum type;
GLvoid * pixels;
int frames = 0;
long start_time = 0;
char time_string[ 100 ] = "FPS:";
void DrawString( GLfloat x, GLfloat y, char * string )
{
glRasterPos2f( x, y );
int len = strlen( string );
for( int i = 0; i < len; i++ )
glutBitmapCharacter( GLUT_BITMAP_9_BY_15, string[ i ] );
}
void DisplayScene()
{
if( !frames++ )
start_time = clock();
float x = glutGet( GLUT_WINDOW_WIDTH ) * rand() /( float ) RAND_MAX;
float y = 20 + glutGet( GLUT_WINDOW_HEIGHT ) * rand() /( float ) RAND_MAX;
glEnable( GL_TEXTURE_2D );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
if( test == TEST_WPBO )
{
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, format, type, pixels );
glColor3fv( White );
glBegin( GL_QUADS );
glTexCoord2f( 0.0, 0.0 );
glVertex2f( x, y );
glTexCoord2f( 1.0, 0.0 );
glVertex2f( x + 64, y );
glTexCoord2f( 1.0, 1.0 );
glVertex2f( x + 64, y + 64 );
glTexCoord2f( 0.0, 1.0 );
glVertex2f( x, y + 64 );
glEnd();
}
if( test == TEST_PBO )
{
glBindBuffer( GL_PIXEL_UNPACK_BUFFER, pbo_id );
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, format, type, NULL );
glColor3fv( White );
glBegin( GL_QUADS );
glTexCoord2f( 0.0, 0.0 );
glVertex2f( x, y );
glTexCoord2f( 1.0, 0.0 );
glVertex2f( x + 64, y );
glTexCoord2f( 1.0, 1.0 );
glVertex2f( x + 64, y + 64 );
glTexCoord2f( 0.0, 1.0 );
glVertex2f( x, y + 64 );
glEnd();
glBindBuffer( GL_PIXEL_UNPACK_BUFFER, 0 );
}
glDisable( GL_TEXTURE_2D );
if( frames == 100 )
{
frames = 0;
sprintf( time_string, "FPS: %i",( int )( 100 * CLOCKS_PER_SEC /( float )( clock() - start_time ) ) );
}
glColor3fv( White );
glRectf( 0.0, 0.0, 200.0, 20.0 );
glColor3fv( Black );
DrawString( 1, 2, time_string );
glFlush();
glutSwapBuffers();
}
void Reshape( int width, int height )
{
glViewport( 0, 0, width, height );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
gluOrtho2D( 0.0, width, 0.0, height );
glClearColor( 1.0, 1.0, 1.0, 1.0 );
glClear( GL_COLOR_BUFFER_BIT );
DisplayScene();
}
void Menu( int value )
{
switch( value )
{
case TEST_PBO:
case TEST_WPBO:
test = value;
glClear( GL_COLOR_BUFFER_BIT );
glutPostRedisplay();
break;
case EXIT:
exit( 0 );
}
}
void GeneratePBO()
{
glGenBuffers( 1, & pbo_id );
glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
glBindBuffer( GL_PIXEL_UNPACK_BUFFER, pbo_id );
GLint size = width * height;
if( format == GL_BGRA )
size *= 4;
else
if( format == GL_BGR )
size *= 3;
glBufferData( GL_PIXEL_UNPACK_BUFFER, size, pixels, GL_STREAM_DRAW );
GLint buf_size;
glGetBufferParameteriv( GL_PIXEL_UNPACK_BUFFER, GL_BUFFER_SIZE, & buf_size );
if( buf_size != size )
{
printf( "Niepoprawny zapis danych do obiektu buforowego\n" );
exit( 0 );
}
glBindBuffer( GL_PIXEL_UNPACK_BUFFER, 0 );
srand( time( NULL ) );
}
void ExtensionSetup()
{
const char * version =( char * ) glGetString( GL_VERSION );
int major = 0, minor = 0;
if( sscanf( version, "%d.%d", & major, & minor ) != 2 )
{
#ifdef WIN32
printf( "Błędny format wersji OpenGL\n" );
#else
printf( "Bledny format wersji OpenGL\n" );
#endif
exit( 0 );
}
if( 10 * major + minor >= 21 )
{
glGenBuffers =( PFNGLGENBUFFERSPROC ) wglGetProcAddress( "glGenBuffers" );
glBindBuffer =( PFNGLBINDBUFFERPROC ) wglGetProcAddress( "glBindBuffer" );
glBufferData =( PFNGLBUFFERDATAPROC ) wglGetProcAddress( "glBufferData" );
glGetBufferParameteriv =( PFNGLGETBUFFERPARAMETERIVPROC )
wglGetProcAddress( "glGetBufferParameteriv" );
}
else
if( glutExtensionSupported( "GL_ARB_pixel_buffer_object" ) )
{
glGenBuffers =( PFNGLGENBUFFERSPROC ) wglGetProcAddress( "glGenBuffersARB" );
glBindBuffer =( PFNGLBINDBUFFERPROC ) wglGetProcAddress( "glBindBufferARB" );
glBufferData =( PFNGLBUFFERDATAPROC ) wglGetProcAddress( "glBufferDataARB" );
glGetBufferParameteriv =( PFNGLGETBUFFERPARAMETERIVPROC )
wglGetProcAddress( "glGetBufferParameterivARB" );
}
else
{
printf( "Brak rozszerzenia ARB_vertex_buffer_object!\n" );
exit( 0 );
}
}
void LoadTARGA( int argc, char * argv[] )
{
if( argc < 2 )
{
printf( "Brak nazwy pliku TARGA\n" );
exit( 1 );
}
if( !load_targa( argv[ 1 ], width, height, format, type, pixels ) )
{
#ifdef WIN32
printf( "Błąd odczytu lub błędny format pliku: %s\n", argv[ 1 ] );
#else
printf( "Blad odczytu lub bledny format pliku: %s\n", argv[ 1 ] );
#endif
exit( 1 );
}
}
int main( int argc, char * argv[] )
{
LoadTARGA( argc, argv );
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
glutInitWindowSize( 500, 500 );
glutCreateWindow( "PBO" );
glutDisplayFunc( DisplayScene );
glutReshapeFunc( Reshape );
glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Test z PBO", TEST_PBO );
glutAddMenuEntry( "Test bez PBO", TEST_WPBO );
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
ExtensionSetup();
GeneratePBO();
glutIdleFunc( DisplayScene );
glutMainLoop();
return 0;
}