Listy wyświetlania (ang. display lists) pełnią ważną rolę w bibliotece OpenGL. Zasada działania list wyświetlania sprowadza się do grupowania (zwanego czasami niesłusznie „kompilowaniem”) poleceń biblioteki OpenGL. Tak utworzona grupa poleceń identyfikowana jest identyfikatorem w postaci liczby całkowitej. Identyfikator ten umożliwia późniejsze wywołanie wybranej listy wyświetlania, czyli wykonanie poleceń biblioteki OpenGL w niej zapamiętanych.
Umiejętne wykorzystanie list wyświetlania ułatwia wielokrotne używanie kodu zawierającego wywołania funkcji biblioteki OpenGL i jednocześnie może się przyczynić do zwiększenia szybkości wyświetlania grafiki. Właśnie ta ostatnia potencjalna możliwość bywa dość często przedmiotem dyskusji, ale trzeba pamiętać, że listy wyświetlania nie stanowią uniwersalnej recepty na przyspieszenie programu. Dodatkowy problem przy optymalizacji programu przy wykorzystaniu list wyświetlania związany jest z tym, że specyfikacja OpenGL pozostawia sposób ich realizacji autorom implementacji biblioteki. Stąd efektywność rozwiązań korzystających z list wyświetlania zależy w dużej mierze właśnie od cech konkretnej implementacji.
Tworzenie listy wyświetlania
Rozpoczęcie generowania nowej listy wyświetlania wymaga wywołania funkcji:
void glNewList( GLuint list, GLenum mode )
której parametr list określa unikatowy identyfikator listy wyświetlania (musi to być liczba większa od 0), a parametr mode definiuje tryb tworzenia listy wyświetlania i może przyjmować jedną z dwóch wartości:
Definiowanie listy wyświetlania kończy wywołanie bezparametrowej funkcji:
Jeżeli podczas generowania listy wyświetlania zabraknie pamięci, lista oczywiście nie zostanie utworzona, a biblioteka OpenGL zwróci kod błędu
GL_OUT_OF_MEMORY.
W liście wyświetlania można umieścić wywołanie innej listy wyświetlania. Specyfikacja biblioteki OpenGL nakłada jednak ograniczenie na maksymalną głębokość zagnieżdżenia takich wywołań. Jest to wartość zależna od implementacji, ale równa co najmniej 64.
Jednocześnie specyfikacja OpenGL nie pozwala na zagnieżdżanie definicji list wyświetlania. Inaczej mówiąc każde wywołanie funkcji glNewList musi być poprzedzone zakończeniem definiowania poprzedniej listy wyświetlania.
Funkcje nie umieszczane na liście wyświetlania
Szereg poleceń biblioteki OpenGL wywołanych wewnątrz listy wyświetlania nie jest do niej włączana, ale jest wykonywana w trybie bezpośrednim. Specyfikacja biblioteki w wersji 2.0 zalicza do nich następujące funkcje lub grupy funkcji: glGenLists, glDeleteLists, glFeedbackBuffer, glSelectBuffer, glRenderMode, glClientActiveTexture, glColorPointer, glEdgeFlagPointer, glFogCoordPointer, glIndexPointer, glInterleavedArrays, glNormalPointer, glSecondaryColorPointer, glTexCoordPointer, glVertexAttribPointer, glVertexPointer, glEnableClientState, glDisableClientState, glEnableVertexAttribArray, glDisableVertexAttribArray, glPushClientAttrib, glPopClientAttrib, glPixelStore, glReadPixels, glGenTextures, glDeleteTextures, glAreTexturesResident, glGenQueries, glDeleteQueries, glGenBuffers, glDeleteBuffers, glBindBuffer, glBufferData, glBufferSubData, glMapBuffer, glUnmapBuffer, glCreateProgram, glCreateShader, glDeleteProgram, glDeleteShader, glAttachShader, glDetachShader, glBindAttribLocation, glCompileShader, glShaderSource, glLinkProgram, glValidateProgram, glFinish, glFlush, glGet, glIs.
Ponadto w trybie bezpośrednim zostaną wykonane funkcje: glTexImage3D, glTexImage2D, glTexImage1D, glHistogram, glColorTable jeżeli zostaną wywołane z parametrami:
GL_PROXY_TEXTURE_3D,
GL_PROXY_TEXTURE_2D,
GL_PROXY_TEXTURE_CUBE_MAP,
GL_PROXY_TEXTURE_1D,
GL_PROXY_HISTOGRAM,
GL_PROXY_COLOR_TABLE,
GL_PROXY_POST_CONVOLUTION_COLOR_TABLE,
GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE (oczywiście poszczególne parametry związane są z odpowiednimi funkcjami).
Usuwanie list wyświetlania
Usunięcie wybranej grupy list wyświetlania umożliwia funkcja:
void glDeleteLists( GLuint list, GLsizei range )
której parametr list oznacza identyfikator pierwszej usuwanej listy wyświetlania, a range ilość kolejnych list do usunięcia. Wskazanie do usunięcia identyfikatorów, które nie są zajęte przez listy wyświetlania jest ignorowane i nie powoduje wystąpienia żadnego błędu. Podobny efekt spowoduje podanie wartości 0 w parametrze range.
Trzeba także pamiętać, że utworzenie listy wyświetlania o identyfikatorze wykorzystywanym już przez inną listę spowoduje usunięcie wcześniejszej listy.
Generowanie identyfikatorów list wyświetlania
Serię identyfikatorów do pustych list wyświetlania generuje funkcja:
GLuint glGenLists( GLsizei range )
Funkcja zwraca numer identyfikatora pierwszej utworzonej listy wyświetlania. Następne utworzone listy mają identyfikatory o kolejnych wartościach aż do range - 1 (oczywiście ilość utworzonych listy wynosi range). W przypadku braku możliwości przydzielenia ciągłego zakresu żądanej ilości identyfikatorów list wyświetlania funkcja glGenLists zwróci wartość 0.
Biblioteka OpenGL umożliwia także sprawdzenie, czy dany identyfikator
jest aktualnie wykorzystywany przez listę wyświetlania. Realizuje to funkcja:
GLboolean glIsList( GLuint list )
która zwraca wartość
GL_TRUE jeżeli identyfikator o wartości list jest wykorzystywany przez listę wyświetlania i
GL_FALSE w przeciwnym wypadku.
Wykonywanie list wyświetlania
Pojedynczą listę wyświetlania o identyfikatorze list najłatwiej jest wykonać wywołując funkcję:
void glCallList( GLuint list )
W przypadku, gdy chcemy kolejno wykonać większą liczbę list wyświetlania można do tego celu użyć funkcji:
void glCallLists( GLsizei n, GLenum type, const GLvoid * lists )
Parametr n określa liczbę elementów tablicy zawierającej numery identyfikatorów wykonywanych list, do której wskaźnik przekazywany jest w parametrze lists. Rodzaj elementów tablicy określa parametr type, który oprócz poznanych wcześniej standardowych typów liczbowych biblioteki OpenGL, tj.:
GL_BYTE,
GL_UNSIGNED_BYTE,
GL_SHORT,
GL_UNSIGNED_SHORT,
GL_INT,
GL_UNSIGNED_INT,
GL_FLOAT, może także przyjąć wartości:
Jak Czytelnik zapewne zauważył współczynniki znajdujące się w podanych wyżej równaniach są kolejnymi potęgami liczby 2: 28, 216 i 224 . Stąd przy obliczaniu wartości identyfikatora listy można zastosować przesuwanie bitowe w lewo zamiast mnożenia.
Wartość identyfikatora listy wyświetlania przekazana do funkcji glCallLists jest jeszcze powiększana o tzw. wartość bazową, którą określa funkcja:
void glListBase( GLuint base )
Początkowa i zarazem domyślna wielkość wartości bazowej wynosi 0.
Program przykładowy
Przykładowy program (plik listy wyswietlania.cpp) wykonuje proste testy wydajności list wyświetlania. W tym celu tworzone są trzy listy zawierające kod renderujący punkt, odcinki i trójkąty. W przypadku punktów (rysunek 1) i odcinków (rysunek 2) na komputerze Autora nie wystąpiły różnice w szybkości renderingu z użyciem list wyświetlania i bez ich wykorzystania. Ale już przy rysowaniu trójkątów zastosowanie list wyświetlania (rysunek 4) dało kilkuprocentowe zwiększenie ilości FPS w stosunku do testu nie korzystającego z listy wyświetlania (rysunek 3). Aby wyniki były całkowicie porównywalne w każdym przypadku do testu używany jest ten sam losowo wybrany zbiór współrzędnych i składowych kolorów.
Plik listy_wyswietlania.cpp
#include <GL/glut.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include "colors.h"
enum
{
POINTS,
POINTS_DL,
LINES,
LINES_DL,
TRIANGLES,
TRIANGLES_DL,
FULL_WINDOW,
ASPECT_1_1,
EXIT
};
int aspect = FULL_WINDOW;
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;
GLuint listid;
const int size = 30000;
GLfloat vertex_x[ size ];
GLfloat vertex_y[ size ];
GLfloat vertex_z[ size ];
GLfloat color_r[ size ];
GLfloat color_g[ size ];
GLfloat color_b[ 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 Points()
{
glBegin( GL_POINTS );
for( int i = 0; i < size; i++ )
{
glColor3f( color_r[ i ], color_g[ i ], color_b[ i ] );
glVertex3f( vertex_x[ i ], vertex_y[ i ], vertex_z[ i ] );
}
glEnd();
}
void Lines()
{
glBegin( GL_LINES );
for( int i = 0; i < size / 2; i++ )
{
glColor3f( color_r[ i ], color_g[ i ], color_b[ i ] );
glVertex3f( vertex_x[ 2 * i + 0 ], vertex_y[ 2 * i + 0 ], vertex_z[ 2 * i + 0 ] );
glVertex3f( vertex_x[ 2 * i + 1 ], vertex_y[ 2 * i + 1 ], vertex_z[ 2 * i + 1 ] );
}
glEnd();
}
void Triangles()
{
glBegin( GL_TRIANGLES );
for( int i = 0; i < size / 3; i++ )
{
glColor3f( color_r[ 3 * i + 0 ], color_g[ 3 * i + 0 ], color_b[ 3 * i + 0 ] );
glVertex3f( vertex_x[ 3 * i + 0 ], vertex_y[ 3 * i + 0 ], vertex_z[ 3 * i + 0 ] );
glColor3f( color_r[ 3 * i + 1 ], color_g[ 3 * i + 1 ], color_b[ 3 * i + 1 ] );
glVertex3f( vertex_x[ 3 * i + 1 ], vertex_y[ 3 * i + 1 ], vertex_z[ 3 * i + 1 ] );
glColor3f( color_r[ 3 * i + 2 ], color_g[ 3 * i + 2 ], color_b[ 3 * i + 2 ] );
glVertex3f( vertex_x[ 3 * i + 2 ], vertex_y[ 3 * i + 2 ], vertex_z[ 3 * i + 2 ] );
}
glEnd();
}
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:
Points();
break;
case POINTS_DL:
glCallList( listid + 0 );
break;
case LINES:
Lines();
break;
case LINES_DL:
glCallList( listid + 1 );
break;
case TRIANGLES:
Triangles();
break;
case TRIANGLES_DL:
glCallList( listid + 2 );
break;
};
glLoadIdentity();
glTranslatef( 0, 0, - near );
glColor3fv( Black );
if( frames == 250 )
{
frames = 0;
sprintf( time_string, "FPS: %i",( int )( 250 * 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:
case POINTS_DL:
case LINES:
case LINES_DL:
case TRIANGLES:
case TRIANGLES_DL:
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 GenerateDisplayLists()
{
srand( time( NULL ) );
for( int i = 0; i < size; i++ )
{
vertex_x[ i ] =( rand() /( float ) RAND_MAX ) * 4 - 2;
vertex_y[ i ] =( rand() /( float ) RAND_MAX ) * 4 - 2;
vertex_z[ i ] =( rand() /( float ) RAND_MAX ) * 4 - 2;
color_r[ i ] =( rand() /( float ) RAND_MAX );
color_g[ i ] =( rand() /( float ) RAND_MAX );
color_b[ i ] =( rand() /( float ) RAND_MAX );
}
listid = glGenLists( 3 );
glNewList( listid + 0, GL_COMPILE );
Points();
glEndList();
glNewList( listid + 1, GL_COMPILE );
Lines();
glEndList();
glNewList( listid + 2, GL_COMPILE );
Triangles();
glEndList();
}
int main( int argc, char * argv[] )
{
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
glutInitWindowSize( 500, 500 );
#ifdef WIN32
glutCreateWindow( "Listy wyświetlania" );
#else
glutCreateWindow( "Listy wyswietlania" );
#endif
glutDisplayFunc( DisplayScene );
glutReshapeFunc( Reshape );
int MenuTest = glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Punkty bez listy wyświetlania", POINTS );
glutAddMenuEntry( "Punkty z listą wyświetlania", POINTS_DL );
glutAddMenuEntry( "Odcinki bez listy wyświetlania", LINES );
glutAddMenuEntry( "Odcinki z listą wyświetlania", LINES_DL );
glutAddMenuEntry( "Trójkąty bez listy wyświetlania", TRIANGLES );
glutAddMenuEntry( "Trójkąty z listą wyświetlania", TRIANGLES_DL );
#else
glutAddMenuEntry( "Punkty bez listy wyswietlania", POINTS );
glutAddMenuEntry( "Punkty z lista wyswietlania", POINTS_DL );
glutAddMenuEntry( "Odcinki bez listy wyswietlania", LINES );
glutAddMenuEntry( "Odcinki z lista wyswietlania", LINES_DL );
glutAddMenuEntry( "Trojkaty bez listy wyswietlania", TRIANGLES );
glutAddMenuEntry( "Trojkaty z lista wyswietlania", TRIANGLES_DL );
#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 );
#ifdef WIN32
glutAddSubMenu( "Test", MenuTest );
glutAddSubMenu( "Aspekt obrazu", MenuAspect );
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
GenerateDisplayLists();
glutIdleFunc( DisplayScene );
glutMainLoop();
return 0;
}