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

Listy wyświetlania

[lekcja] Rozdział 14. Zasada działania listy wyświetlania; tworzenie i usuwanie listy wyświetlania; wykaz funkcji nie umieszczanych na liście wyświetlania; generowanie identyfikatorów list wyświetlania; program przykładowy.
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:
C/C++
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:
  • GL_COMPILE - lista generowana w trybie zachowanym (ang. retained mode); polecenia umieszczane są na liście ale nie są wykonywane,
  • GL_COMPILE_AND_EXECUTE - lista generowana w trybie bezpośrednim (ang. immediate mode); polecenia umieszczane są na liście i jednocześnie natychmiast wykonywane.
Definiowanie listy wyświetlania kończy wywołanie bezparametrowej funkcji:
C/C++
void glEndList()
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:
C/C++
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:
C/C++
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:
C/C++
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ę:
C/C++
void glCallList( GLuint list )
W przypadku, gdy chcemy kolejno wykonać większą liczbę list wyświetlania można do tego celu użyć funkcji:
C/C++
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:
  • GL_2_BYTES - tablica lists zawiera pary b0b1 liczb typu GL_UNSIGNED_BYTE, przy czym numer identyfikatora listy obliczany jest wg wzoru:
    256*b0 + b1
  • GL_3_BYTES - tablica lists zawiera trójki b0b1b2 liczb typu GL_UNSIGNED_BYTE, przy czym numer identyfikatora listy obliczany jest wg wzoru:
    65536*b0 + 256*b1 + b2
  • GL_4_BYTES - tablica lists zawiera czwórki b0 b1 b2b3 liczb typu GL_UNSIGNED_BYTE, przy czym numer identyfikatora listy obliczany jest wg wzoru:
    16777216*b0 + 65536*b1 + 256*b2 + b3
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:
C/C++
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

Rysunek 1. Program Listy wyświetlania - test rysowania punktów
Rysunek 1. Program Listy wyświetlania - test rysowania punktów
Rysunek 2. Program Listy wyświetlania - test rysowania odcinków
Rysunek 2. Program Listy wyświetlania - test rysowania odcinków
Rysunek 3. Program Listy wyświetlania - test rysowania trójkątów bez listy wyświetlania
Rysunek 3. Program Listy wyświetlania - test rysowania trójkątów bez listy wyświetlania
Rysunek 4. Program Listy wyświetlania - test rysowania trójkątów z listą wyświetlania
Rysunek 4. Program Listy wyświetlania - test rysowania trójkątów z listą wyświetlania
C/C++
/*
(c) Janusz Ganczarski
http://www.januszg.hg.pl
JanuszG@enter.net.pl
*/

#include <GL/glut.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include "colors.h"

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

enum
{
    // rodzaj testu
    POINTS, // punkty bez listy wyświetlania
    POINTS_DL, // punkty z listą wyświetlania
    LINES, // odcinki bez listy wyświetlania
    LINES_DL, // odcinki z listą wyświetlania
    TRIANGLES, // trójkąty bez listy wyświetlania
    TRIANGLES_DL, // trójkąty z listą wyświetlania
   
    // obszar renderingu
    FULL_WINDOW, // aspekt obrazu - całe okno
    ASPECT_1_1, // aspekt obrazu 1:1
    EXIT // wyjście
};

// aspekt obrazu

int aspect = FULL_WINDOW;

// rozmiary bryły obcinania

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;

// rodzaj testu - domyślnie punkty bez listy wyświetlania

int test = POINTS;

// identyfikator pierwszej listy wyświetlania

GLuint listid;

// ilość generowanych współrzędnych prymitywów

const int size = 30000;

// współrzędne prymitywów

GLfloat vertex_x[ size ];
GLfloat vertex_y[ size ];
GLfloat vertex_z[ size ];

// kolory prymitywów

GLfloat color_r[ size ];
GLfloat color_g[ size ];
GLfloat color_b[ size ];

// licznik ramek (FPS)

int frames = 0;

// licznik czasu

long start_time = 0;

// tablica znaków ze wartością FPS

char time_string[ 100 ] = "FPS:";

// funkcja rysująca napis w wybranym miejscu

void DrawString( GLfloat x, GLfloat y, char * string )
{
    // położenie napisu
    glRasterPos2f( x, y );
   
    // wyświetlenie napisu
    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();
}

// test renderingu odcinków

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();
}

// test renderingu trójkątów

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();
}

// funkcja generująca scenę 3D

void DisplayScene()
{
    // licznik czasu
    if( !frames++ )
         start_time = clock();
   
    // kolor tła - zawartość bufora koloru
    glClearColor( 1.0, 1.0, 1.0, 1.0 );
   
    // czyszczenie bufora koloru i bufora głębokości
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
   
    // wybór macierzy modelowania
    glMatrixMode( GL_MODELVIEW );
   
    // macierz modelowania = macierz jednostkowa
    glLoadIdentity();
   
    // przesunięcie układu współrzędnych obiektu do środka bryły odcinania
    glTranslatef( 0, 0, -( near + far ) / 2 );
   
    // włączenie testu bufora głębokości
    glEnable( GL_DEPTH_TEST );
   
    switch( test )
    {
        // punkty bez listy wyświetlania
    case POINTS:
        Points();
        break;
       
        // punkty z listą wyświetlania
    case POINTS_DL:
        glCallList( listid + 0 );
        break;
       
        // odcinki bez listy wyświetlania
    case LINES:
        Lines();
        break;
       
        // odcinki z listą wyświetlania
    case LINES_DL:
        glCallList( listid + 1 );
        break;
       
        // trójkąty bez listy wyświetlania
    case TRIANGLES:
        Triangles();
        break;
       
        // trójkąty z listą wyświetlania
    case TRIANGLES_DL:
        glCallList( listid + 2 );
        break;
    };
   
    // trzeba odpowiednio przekształcić układ współrzędnych
    // aby napis znajdował się na samej "górze" bryły obcinania
    glLoadIdentity();
    glTranslatef( 0, 0, - near );
   
    // komunikat o ilości ramek rysowanych na sekundę (FPS)
    glColor3fv( Black );
    if( frames == 250 )
    {
        frames = 0;
        sprintf( time_string, "FPS: %i",( int )( 250 * CLOCKS_PER_SEC /( float )( clock() - start_time ) ) );
    }
   
    // narysowanie napisu
    DrawString( left, bottom, time_string );
   
    // 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();
   
    // parametry bryły obcinania
    if( aspect == ASPECT_1_1 )
    {
        // wysokość okna większa od wysokości okna
        if( width < height && width > 0 )
             glFrustum( left, right, bottom * height / width, top * height / width, near, far );
        else
       
        // szerokość okna większa lub równa wysokości okna
        if( width >= height && height > 0 )
             glFrustum( left * width / height, right * width / height, bottom, top, near, far );
       
    }
    else
         glFrustum( left, right, bottom, top, near, far );
   
    // generowanie sceny 3D
    DisplayScene();
}

// obsługa menu podręcznego

void Menu( int value )
{
    switch( value )
    {
        // wybór rodzaju testu
    case POINTS:
    case POINTS_DL:
    case LINES:
    case LINES_DL:
    case TRIANGLES:
    case TRIANGLES_DL:
        test = value;
        DisplayScene();
        break;
       
        // obszar renderingu - całe okno
    case FULL_WINDOW:
        aspect = FULL_WINDOW;
        Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
        break;
       
        // obszar renderingu - aspekt 1:1
    case ASPECT_1_1:
        aspect = ASPECT_1_1;
        Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
        break;
       
        // wyjście
    case EXIT:
        exit( 0 );
    }
}

// utworzenie list wyświetlania

void GenerateDisplayLists()
{
    // określenie zarodka dla  ciągu liczb pseudolosowych
    srand( time( NULL ) );
   
    // generowanie położenia punktów (wierzchołków) figur oraz ich kolorów
    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 );
    }
   
    // wygenerowanie identyfikatorów trzech list wyświetlania
    listid = glGenLists( 3 );
   
    // utworzenie pierwszej listy - test rysowania punktów
    glNewList( listid + 0, GL_COMPILE );
    Points();
    glEndList();
   
    // utworzenie drugiej listy - test rysowania odcinków
    glNewList( listid + 1, GL_COMPILE );
    Lines();
    glEndList();
   
    // utworzenie pierwszej listy - test rysowania trójkątów
    glNewList( listid + 2, GL_COMPILE );
    Triangles();
    glEndList();
}

int main( int argc, char * argv[] )
{
    // inicjalizacja biblioteki GLUT
    glutInit( & argc, argv );
   
    // inicjalizacja bufora ramki
    glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
   
    // rozmiary głównego okna programu
    glutInitWindowSize( 500, 500 );
   
    // utworzenie głównego okna programu
    #ifdef WIN32
   
    glutCreateWindow( "Listy wyświetlania" );
    #else
   
    glutCreateWindow( "Listy wyswietlania" );
    #endif
   
    // dołączenie funkcji generującej scenę 3D
    glutDisplayFunc( DisplayScene );
   
    // dołączenie funkcji wywoływanej przy zmianie rozmiaru okna
    glutReshapeFunc( Reshape );
   
    // utworzenie podmenu - Test
    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
   
    // utworzenie podmenu - Aspekt obrazu
    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 );
   
    // menu główne
    glutCreateMenu( Menu );
    #ifdef WIN32
   
    glutAddSubMenu( "Test", MenuTest );
    glutAddSubMenu( "Aspekt obrazu", MenuAspect );
    glutAddMenuEntry( "Wyjście", EXIT );
    #else
   
    glutAddMenuEntry( "Wyjscie", EXIT );
    #endif
   
    // określenie przycisku myszki obsługującego menu podręczne
    glutAttachMenu( GLUT_RIGHT_BUTTON );
   
    // utworzenie list wyświetlania
    GenerateDisplayLists();
   
    // funkcja bezczynności
    glutIdleFunc( DisplayScene );
   
    // wprowadzenie programu do obsługi pętli komunikatów
    glutMainLoop();
    return 0;
}
Poprzedni dokument Następny dokument
Światła i materiały Mieszanie kolorów