Сообщений 0    Оценка 0        Оценить  
Система Orphus

Списки отображения

Глава из книги “OpenGL. Руководство по программированию”

Авторы: Д. Шрайнер
М. Ву
Дж. Нейдер
Т. Девис

Источник: OpenGL. Руководство по программированию
Материал предоставил: Издательство "Питер"
Опубликовано: 28.02.2006
Версия текста: 1.0
Для чего нужны списки отображения
Пример списка отображения
Философия проектирования списков отображения
Создание и выполнение списка отображения
Присвоение имени списку отображения и создание списка
Что сохраняется в списке отображения?
Выполнение списка отображения
Иерархические списки отбражения
Управление индексацией списков отображений
Выполнение составных списков отображения
Управление параметрами состояния с помощью списков отображения
Инкапсуляция изменений режима

Список отображения (display list) — иначе список команд или список вывода — это группа команд OpenGL, сохраненных для последующего выполнения. Когда список отображения вызывается на выполнение, команды обрабатываются в порядке их появления в списке. Большинство команд OpenGL может сохраняться в списке или обрабатываться в режиме непосредственного выполнения, для краткости далее именуемом непосредственным (immediate) режимом, то есть незамедлительно. Вы можете свободно смешивать непосредственный режим и списки отображения в рамках одной программы. В примерах, приводимых ранее, до сих пор использовался непосредственный режим. В этой главе обсуждается, что такое списки отображения и каким образом их лучше использовать. Соответственно, ее разделы посвящены следующим темам:

Для чего нужны списки отображения

Списки отображения позволяют улучшить производительность за счет использования запомненных для последующего выполнения команд OpenGL. Максимум отдачи дает кэширование команд в списке, если вы планируете перерисовывать один и тот же объект несколько раз или вы имеете набор изменяемых состояний, которые нужно воспроизводить многократно. С помощью списков отображения вы можете определить геометрию и/или изменяемое состояние однажды и повторить их столько раз, сколько нужно.

Случай, когда вы используете список отображения для единичного запоминания, проиллюстрируем примером рисования трехколесного велосипеда. Его два задних колеса имеют одинаковый размер, но требуют отдельной прорисовки, так как смещены друг относительно друга. Переднее колесо больше по диаметру и также находится в индивидуальной позиции. Эффективный способ передать вращение колес заключается в запоминании геометрии одного колеса в списке отображения и последующем трехкратном вызове этого списка. Вам нужно построить соответствующую модельно-видовую матрицу перед каждым вызовом списка для вычисления корректных размеров и положения каждого колеса.

Когда программы OpenGL запускаются удаленно с другого компьютера или по сети, особенно важно кэшировать команды в списке отображения. В этом случае сервер является компьютером, отличным от хост-машины. (Информацию о клиент-серверной модели OpenGL см. в разделе “Что такое OpenGL” главы 1.)

Поскольку списки отображения являются частью состояния сервера и, следовательно, постоянно размещены на сервере, вы можете сократить расходы на передачу по сети повторно используемых данных, если сохраните повторно используемые команды в списке отображения.

В варианте локального запуска вы также можете добиться повышения производительности за счет помещения наиболее часто используемых команд в список отображения. Некоторые аппаратные графические средства способны сохранять списки отображения в специально выделенной области памяти или запоминать данные в форме, обеспечивающей максимум совместимости с программным обеспечением. (Более подробно вопросы оптимизации обсуждаются в разделе “Философия проектирования списков отображения”.)

Пример списка отображения

Список отображения — это удобный и эффективный способ именования и организации набора команд OpenGL. Например, допустим, что вы хотите нарисовать тор и просмотреть его с различных углов зрения. Лучший способ сделать это — сохранить тор в списке отображения. Тогда всякий раз при изменении представления вам нужно будет изменить модельно-видовую матрицу и выполнить список отображения, рисующий тор. Листинг 7.1 иллюстрирует сказанное.

Листинг 7.1. Создание списка отображения: torus.c
GLuint theTorus;
/* Рисование тора */
static void torus( int numc, int numt )
{
   int i, j, k;
   double s, t, x, y, z, twopi;
   twopi = 2 * ( double ) M_PI;

   for ( i = 0; i < numc; i++ )
   {
      glBegin( GL_QUAD_STRIP );

      for ( j = 0; j <= numt; j++ )
      {
         for ( k = 1; k >= 0; k-- )
         {
            s = ( i + k ) % numc + 0.5;
            t = j % numt;
            x = ( 1 + .1 * cos( s * twopi / numc ) ) * cos( t * twopi / numt );
            y = ( 1 + .1 * cos( s * twopi / numc ) ) * sin( t * twopi / numt );
            z = .1 * sin( s * twopi / numc );
            glVertex3f( x, y, z );
         }
      }

      glEnd();
   }
}

/* Создание списка отображения с тором и инициализация */
static void init( void )
{
   theTorus = glGenLists( 1 );
   glNewList( theTorus, GL_COMPILE );
   torus( 8, 25 );
   glEndList();
   glShadeModel( GL_FLAT );
   glClearColor( 0.0, 0.0, 0.0, 0.0 );
}

void display( void )
{
   glClear( GL_COLOR_BUFFER_BIT );
   glColor3f( 1.0, 1.0, 1.0 );
   glCallList( theTorus );
   glFlush();
}

void reshape( int w, int h )
{
   glViewport( 0, 0, ( GLsizei ) w, ( GLsizei ) h );
   glMatrixMode( GL_PROJECTION );
   glLoadIdentity();
   gluPerspective( 30, ( GLfloat ) w / ( GLfloat ) h, 1.0, 100.0 );
   glMatrixMode( GL_MODELVIEW );
   glLoadIdentity();
   gluLookAt( 0, 0, 10, 0, 0, 0, 0, 1, 0 );
}

/* Поворот относительно оси x при нажатии клавиши "x"; вращение вокруг оси y
при нажатии клавиши "y"; при нажатии клавиши "i" тор возвращается
в оригинальное представление */
void keyboard( unsigned char key, int x, int y )
{
   switch ( key )
   {

      case 'x':

      case 'X':
         glRotatef( 30., 1.0, 0.0, 0.0 );
         glutPostRedisplay();
         break;

      case 'y':

      case 'Y':
         glRotatef( 30., 0.0, 1.0, 0.0 );
         glutPostRedisplay();
         break;

      case 'i':

      case 'I':
         glLoadIdentity();
         gluLookAt( 0, 0, 10, 0, 0, 0, 0, 1, 0 );
         glutPostRedisplay();
         break;

      case 27:
         exit( 0 );
         break;
   }
}

int main( int argc, char **argv )
{
   glutInitWindowSize( 200, 200 );
   glutInit( &argc, argv );
   glutInitDisplayMode( GLUT_SINGLE | GLUT_RGB );
   glutCreateWindow( argv[ 0 ] );
   init();
   glutReshapeFunc( reshape );
   glutKeyboardFunc( keyboard );
   glutDisplayFunc( display );
   glutMainLoop();
   return 0;
}

В первую очередь посмотрим на процедуру init(). Она создает список отображения для тора и устанавливает начальные состояния переменных рендеринга. Заметьте, что подпрограмма для рисования тора (torus()) обрамлена командами glNewList() и glEndList(), которые определяют начало и конец списка отображения соответственно. Аргумент listName метода glNewList() — это целочисленный индекс, сгенерированный командой glGenLists(), уникальным образом идентифицирующий список отображения.

Пользователь может вращать тор относительно оси x или y, нажимая клавиши X или Y, когда окно получает фокус клавиатуры. Всякий раз при этом происходит вызов функции keyboard(), которая накладывает матрицу поворота на 30_ (относительно осей x и y) на текущую модельно-видовую матрицу. Команда glut- PostRedisplay() своим вызовом влечет перерисовку тора функцией display() в цикле glutMainLoop() после того, как будут обработаны другие события.

По нажатии клавиши I функция keyboard() восстанавливает начальную модельно-видовую матрицу и возвращает тор в оригинальное положение. Процедура display() очень проста. Она очищает окно, а затем вызывает команду glCallList() для выполнения команд списка отображения. Если бы мы не использовали списки отображения, процедура display() выдавала бы распоряжение на перерисовку тора при каждом своем вызове.

Список отображения содержит исключительно OpenGL-команды. В листинге 7.1 в таком списке сохранены только вызовы glBegin(), glVertex() и glEnd(). Их параметры являются вычисляемыми, а результирующие значения копируются в список отображения перед его созданием. Все тригонометрические расчеты производятся только единожды, с целью увеличить производительность рендеринга. Значения в списке отображения не могут быть изменены позднее, а команда, помещенная в список, — из него удалена. Также вы не можете добавить новые команды в список после того, как он был описан. То есть вы вправе удалить имеющийся список и создать вместо него новый, но не редактировать уже определенный.

ПРИМЕЧАНИЕ

Списки отображения хорошо сочетаются с командами библиотеки GLU, поскольку эти операции в конечном счете разбиваются на команды OpenGL нижнего уровня, которые проще сохранить в списке отображений. Использование списков отображения совместно с GLU особенно важно для оптимизации производительности “мозаичных” команд (tessellators) библиотек GLU (см. главу 11) и NURBS (глава 12).

Философия проектирования списков отображения

С целью поднятия производительности команды списков отображения OpenGL кэшируются, причем так, что однажды созданный список отображения не может быть модифицирован. В противном случае выигрыш в производительности был бы сведен к минимуму за счет поиска по списку и лишних операций управления памятью. Если бы части списка отображения были изменяемыми, распределение и высвобождение памяти привело бы к дефрагментации памяти. Любые модификации, внесенные реализацией OpenGL в порядок команд списка отображения с целью повышения эффективности рендеринга, все равно потребовали бы отката. Также вероятны были бы трудности с доступом к списку, кэшированному где-нибудь в сети или на системной шине.

Способ, которым достигается оптимизация команд в списке отображения, может варьироваться от реализации к реализации. Например, такая простая команда, как glRotate*(), может выполняться по-разному быстро, если она помещена в список отображения, поскольку вычисления для получения матрицы вращения далеко не тривиальны (они могут включать в себя извлечение квадратного корня и тригонометрические функции). В списке отображения, однако, требуется сохранение только конечной матрицы вращения, так что время выполнения любой команды вращения из состава списка отображения обусловлено тем, насколько быстро обрабатывает аппаратное обеспечение команду glMultMatrix*(). Изощренные реализации OpenGL могут даже объединять смежные команды преобразования в одном произведении матриц.

Хотя не гарантируется, что ваша реализация OpenGL оптимизирует списки отображения для любых специфических случаев, выполнение списка отображения никогда не будет медленнее, чем выполнение тех же команд в индивидуальном порядке. Вместе с тем имеются некоторые издержки, обусловленные процессом перехода к списку отображения. Если такой список невелик, эти издержки способны свести на нет все преимущества списочной организации. Наиболее предпочтительные места для оптимизации перечислены ниже, со ссылками на главы, где эти темы рассматриваются:

ПРИМЕЧАНИЕ

Для оптимизации текстурных изображений вам будет лучше сохранить данные текстур в объектах текстур, а не в списках отображений.

Некоторые из перечисленных здесь команд, используемых для указания свойств, являются контекстно-зависимыми, и вы должны принимать это во внимание, если хотите добиться максимальной производительности. Например, если активен режим GL_COLOR_MATERIAL, для отдельных свойств материала будет отслеживаться текущий цвет (см. главу 5). Любые вызовы glMaterial*(), устанавливающие свойства материала, будут игнорироваться. Увеличить производительность можно за счет сохранения переменных состояния вместе с параметрами геометрии. Допустим, вы хотите применить преобразования к части геометрических объектов, а затем вывести общий результат. Тогда ваш код может выглядеть следующим образом:

glNewList(1, GL_COMPILE);
draw_some_geometric_objects();
glEndList();
glLoadMatrix(M);
glCallList(1);

Но если геометрические объекты должны быть трансформированы таким образом несколько раз, лучше сохранять матрицу в списке отображения. Например, если вы напишете следующий код, в отдельных реализациях вы получите дополнительное увеличение производительности за счет трансформации объектов каждый раз при их отрисовке:

glNewList(1, GL_COMPILE);
glLoadMatrix(M);
draw_some_geometric_objects();
glEndList();
glCallList(1);

Более приятная ситуация имеет место при рендеринге изображений. Как вы увидите в главе 8, вы можете модифицировать переменные состояния для передачи пикселов (pixel-transfer), и управлять характером попиксельной развертки изображений и двоичных карт. Если команды, устанавливающие эти переменные состояния, в списке отображения предшествуют определению изображений или двоичных карт, реализация может выполнить эти операции упредительно и поместить результаты в кэш.

Помните, что списки отображений не являются панацеей. Очень маленькие списки не всегда имеют смысл в связи с издержками на выполнение самого списка. Другой недостаток заключается в неизменности содержимого списка отображения. Чтобы производительность была максимальной, списки изображений OpenGL делаются постоянными и недоступными для чтения содержимого. Если приложение нуждается в поддержании используемых в списке данных вне этого списка (например, для последующей их обработки), это увеличивает требования к объему необходимой памяти.

Создание и выполнение списка отображения

Как вы уже видели, команды glNewList() и glEndList() используются для указания места начала и окончания определения списка отображения, а сам список вызывается на выполнение с помощью идентифицирующего его индекса, генерируемого glCallList(). В листинге 7.2 список отображения создается в подпрограмме init(). Этот список отображения содержит команды OpenGL для рисования красного треугольника. В процедуре display() список отображения выполняется 10 раз. Дополнительно в непосредственном режиме рисуется линия. Заметьте, что список отображения выделяет память для размещения команд и значений необходимых переменных.

Листинг 7.2. Использование списка отображения: list.c
GLuint listName;
static void init( void )
{
   listName = glGenLists( 1 );
   glNewList( listName, GL_COMPILE );
   glColor3f( 1.0, 0.0, 0.0 ); /* текущий цвет - красный */
   glBegin( GL_TRIANGLES );
   glVertex2f( 0.0, 0.0 );
   glVertex2f( 1.0, 0.0 );
   glVertex2f( 0.0, 1.0 );
   glEnd();
   glTranslatef( 1.5, 0.0, 0.0 ); /* перемещение позиции */
   glEndList();
   glShadeModel( GL_FLAT );
}

static void drawLine( void )
{
   glBegin( GL_LINES );
   glVertex2f( 0.0, 0.5 );
   glVertex2f( 15.0, 0.5 );
   glEnd();
}

void display( void )
{
   GLuint i;
   glClear( GL_COLOR_BUFFER_BIT );
   glColor3f( 0.0, 1.0, 0.0 ); /* текущий цвет - зеленый */

   for ( i = 0; i < 10; i++ )  /* рисуются 10 треугольников */
      glCallList( listName );

   drawLine(); /* эта линия зеленая? НЕТ! */

   /* Где нарисована линия? */
   glFlush();
}

Команда glTranslatef() перемещает позицию рисования в соответствии с положением следующего объекта. Без этого треугольник рисовался бы поверх себя самого. Эти десять вызовов glTranslatef() также воздействуют на выполнение процедуры drawLine(), вызываемой в непосредственном режиме. Поэтому, если вы обращаетесь к командам преобразования изнутри списка отображения, не забывайте о вносимом ими на всю программу эффекте.

Одновременно может выполняться только один список отображения. Другими словами, вы должны, когда придет время, следом за glNewList() вызвать glEndList(), чтобы указать конец одного списка перед началом выполнения другого. Конечно, команда glEndList() без сопутствующей ей открывающей команды влечет ошибку GL_INVALID_OPERATION. (См. раздел “Обработка ошибок” главы 14 для получения более подробной информации об обработке ошибок.)

Присвоение имени списку отображения и создание списка

Каждый список отображения идентифицируется целочисленным индексом. При создании списка отображения вы должны быть внимательны, чтобы ненароком не указать уже используемый номер и, таким образом, не перезаписать существующий список отображения новым. Соответственно, во избежание путаницы используйте команду glGenLists() для генерирования одного или более ранее не использованных индексов. GLuint glGenLists(GLsizei range);

Выделяет диапазон предварительно высвобожденных смежных индексов (range). Возвращаемое целое число — это индекс, соответствующий началу блока смежных индексов. Все индексы в блоке при этом помечаются как используемые, и последующие вызовы glGenLists() не вернут эти значения, пока вы не удалите их собственноручно. Нуль возвращается, если требуемое количество индексов не может быть выделено или если аргумент range сам равен нулю.

В следующем примере запрашивается один новый индекс, проверяется результат запроса и, если все в порядке, создается список отображения:

listIndex = glGenLists( 1 );

if ( listIndex != 0 )
{
   glNewList( listIndex, GL_COMPILE );
   ...
   glEndList();
}
ПРИМЕЧАНИЕ

Ноль не является корректным индексом.

Указывает начало списка отображения. Команды OpenGL, вызываемые в дальнейшем (пока не встретится glEndList()), помещаются в список, за исключением тех команд, которые по определению не могут быть в нем сохранены. (Эти команды обрабатываются немедленно, во время создания списка.) Аргумент list — это ненулевое положительное целое число, уникальным образом идентифицирующее список. Возможные значения для аргумента mode: GL_COMPILE и GL_COMPILE_AND_EXECUTE. Используйте GL_COMPILE, если хотите, чтобы команды OpenGL обрабатывались во время их размещения в списке; чтобы команды до помещения их в список выполнялись в непосредственном режиме, укажите GL_COMPILE_AND_EXECUTE.

Помечает конец списка отображения. При создании списка отображения запоминается текущий контекст OpenGL. Таким образом, когда контекст разрушается, список отображения также уничтожается. Некоторые оконные системы поддерживают множество контекстов, разделяемых между собой различными списками отображения. В этом случае список отображения разрушается, когда уничтожается последний контекст в общей группе.

Что сохраняется в списке отображения?

Когда вы формируете список отображения, в нем сохраняются только значения выражений. Если значения в массиве впоследствии изменяются, значения в списке изменяются также. В следующем фрагменте кода список отображения содержит команду, устанавливающую в качестве текущего цвета RGBA черный — (0.0, 0.0, 0.0).

Последующее изменение значения массива color_vector на красный цвет (1.0, 0.0, 0.0) не оказывает эффекта на список отображений, поскольку последний содержит значения, которые были актуальны на момент его создания:

GLfloat color_vector[3] = {0.0, 0.0, 0.0};
glNewList(1, GL_COMPILE);
glColor3fv(color_vector);
glEndList();
color_vector[0] = 1.0;

Не все команды OpenGL могут быть помещены в список отображения и выполнены таким образом. Например, ими не могут быть команды, которые устанавливают состояние клиента, и команды, возвращающие значения параметров состояния. (Их в основном легко опознать — так как они возвращают значения параметров, передаваемых по ссылке, или возвращают значения напрямую.) Такие команды при включении их в список отображения выполняются немедленно.

В табл. 7.1 перечислены команды OpenGL, которые можно включить в список отображения. (Заметьте, что glNewList() генерирует ошибку, если она вызывается изнутри еще не завершенного списка.) Некоторые из этих команд нами еще не рассматривались, но вы можете найти их описание в тексте книги с помощью предметного указателя.

glAreTexturesResident() glFlush() glNormalPointer()
glClientActiveTexture() glFogCoordPointer() glPixelStore()
glColorPointer() glGenLists() glPopClientAttrib()
glDeleteLists() glGenTextures() glPushClientAttrib()
glDeleteTextures() glGet*() glReadPixels()
glDisableClientState() glIndexPointer() glRenderMode()
glEdgeFlagPointer() glInterleavedArrays() glSecondaryColorPointer()
glEnableClientState() glIsEnabled() glSelectBuffer()
glFeedbackBuffer() glIsList() glTexCoordPointer()
glFinish() glIsTexture() glVertexPointer()
Таблица 7.1. Функции OpenGL, допустимые к помещению в списке отображения

Для четкого понимания причины этих ограничений вспомните, что в сетевом окружении клиент OpenGL может находиться на одном компьютере, а сервер — на другом. Созданный список отображения постоянно хранится на сервере, таким образом, сервер не может рассчитывать на клиента в плане получения информации, связанной со списком отображения. Если запрашивающие команды, такие как glGet*() или glIs*(), разрешить к использованию в списках отображения, вызывающая программа будет зависеть от непредсказуемости времени возврата данных через сеть. Пока у нее не будет списка отображения в оригинальном виде, в каком он послан, вызывающая программа не будет знать, где ей разместить данные.

Таким образом, любые команды, возвращающие значения, не могут быть помещены в список отображения. Команды, изменяющие состояние клиента, такие как glPixelStore(), glSelectBuffer(), и команды, описывающие массивы вершин, также не могут быть сохранены в списке.

Например, подпрограммы спецификации вершин массива (такие как glVertex-Pointer(), glColorPointer() и glInterleavedArrays()), устанавливают указатели состояния клиента и тоже не могут быть сохранены в списке отображения. Команды glArrayElement(), glDrawArrays() и glDrawElements() посылают данные для построения примитивов из элементов, принадлежащих доступным массивам, соответственно, эти операции могут быть сохранены в списке отображения. (См. раздел “Массивы вершин” главы 2.) Данные массива вершин, сохраненные в списке отображения, извлекаются через разыменование указателей, но не непосредственно. Таким образом, дальнейшие изменения значений в вершинах не будут влиять на определение примитивов в списке отображения.

К влияющим командам относятся и любые команды, использующие режимы сохранения пикселов (pixel-storage modes). (См. раздел “Режимы хранения пикселов ” главы 8.) Другие подпрограммы, полагающиеся на состояние клиента, такие как glFlush() и glFinish(), не могут быть сохранены в списке отображения, поскольку они зависят от состояния клиента в момент обработки списка.

Выполнение списка отображения

После того как вы создали список отображения, вы можете запустить его на выполнение вызовом glCallList(). Разумеется, один и тот же список отображения можно выполнять многократно, а также можно смешивать его обработку с командами непосредственного режима, как мы это уже видели.

Выполняет список отображения, указанный аргументом list. Команды списка отображения выполняются в порядке своего сохранения, как если бы списка отбражения и не было. Если аргумент list не определен, ничего не происходит. Вы можете вызывать glCallList() из любой точки программы, пока активен соответствующий ему контекст OpenGL (тот же, что был активен на момент создания списка, или контекст из общей группы). Список отображения может быть создан в одной подпрограмме, а выполнен — в другой, благодаря его уникальной идентификации по индексу. А вот для сохранения списка отображения в файле или, наоборот, для загрузки его из файла средств не имеется. Это лишнее подтверждение временного характера списков отображения.

Иерархические списки отбражения

Вы можете создать иерархический список отображения, в котором изнутри одного списка отображения — между glNewList() и glEndList() — выполняется вызов других списков той же командой glCallList(). Иерархически упорядоченные списки полезны для объектов, составленных из готовых компонентов, особенно если последние используются больше одного раза. Например, следующий список отображения выводит велосипед, причем отдельные детали конструкции велосипеда (руль, рама и колеса) отображаются собственными списками:

glNewList(listIndex,GL_COMPILE);
glCallList(handlebars);
glCallList(frame);
glTranslatef(1.0, 0.0, 0.0);
glCallList(wheel);
glTranslatef(3.0, 0.0, 0.0);
glCallList(wheel);
glEndList();

Это не позволит вам реализовать полноценную глубокую рекурсию, так как вложенность списков отображения ограничена значением 64, но это число может быть и больше, в зависимости от реализации. Для получения информации о максимальной вложенности списков для конкретной реализации OpenGL воспользуйтесь вызовом

glGetIntegerv(GL_MAX_LIST_NESTING, GLint *data);

OpenGL позволяет создавать списки отображения, которые вызывают еще не существующие списки. При этом ничего не происходит.

Наконец, иерархическая структура — просто удобное средство компоновки нескольких списков более низкого уровня в смысле последующего редактирования, как в листинге 7.3. Здесь внешний список отображения объединяет описания отдельных вершин многоугольника.

Листинг 7.3. Иерархический список отображения
glNewList(1,GL_COMPILE);
glVertex3fv(v1);
glEndList();
glNewList(2,GL_COMPILE);
glVertex3fv(v2);
glEndList();
glNewList(3,GL_COMPILE);
glVertex3fv(v3);
glEndList();
glNewList(4,GL_COMPILE);
glBegin(GL_POLYGON);
glCallList(1);
glCallList(2);
glCallList(3);
glEnd();
glEndList();

Чтобы воспроизвести многоугольник, вызывается список с номером 4. Чтобы отредактировать отдельную вершину, нужно всего лишь пересоздать соответствующий этой вершине вложенный список. Удобно то, что, когда вы создадите новый список под старым индексом, существующий список автоматически будет удален. Это не значит, что распределение памяти будет оптимальным для достижения максимальной производительности, но в иных случаях это лучше чем ничего.

Управление индексацией списков отображений

Пока что мы советовали не пренебрегать командой glGenLists(), чтобы всегда быть уверенными в уникальности индексов списков отображения. Если это по каким-либо причинам вас не устраивает, воспользуйтесь командой glIsList(), позволяющей узнать, свободен ли указанный индекс.

Возвращает GL_TRUE, если аргумент list представляет уже используемый индекс, и GL_FALSE в противном случае.

Вы можете явно удалить указанный список отображения или набор списков с последовательными индексами при помощи вызова glDeleteLists(). Соответствующие индексы становятся снова доступными.

Удаляет заданное количество (range) списков отображения, начиная от заданного индексом list. При попытке удаления несуществующего списка ничего не происходит.

Выполнение составных списков отображения

OpenGL предоставляет эффективный механизм последовательного выполнения нескольких списков отображения. Этот механизм требует, чтобы вы поместили индексы списков в массив и воспользовались вызовом glCallLists(). Очевидно, что применение данного механизма имеет смысл в случае, когда индексы соответствуют каким-либо выразительным значениям. Так, если вы создаете шрифт, каждый индекс будет ассоциирован с ASCII-кодом символа шрифта. Чтобы иметь несколько таких шрифтов, вам нужно задать для них различные начальные индексы, а сделать это легко с помощью вызова glListBase() перед выполнением glCallLists().

Задает смещение, добавляемое к индексам списка отображения. По умолчанию принято значение 0. Это смещение не имеет эффекта ни на команду glCallList(), примененную к одиночному списку отображения, ни на команду glNewList().

Выполняет n списков отбражения. Целевые индексы вычисляются путем сложения базового смещения (заданного glListBase()) с целыми числами массива, указанного аргументом lists.

Аргумент type обозначает тип данных элемента массива: GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT или GL_FLOAT.

Соответственно, понимается, что с аргументом lists нужно обращаться как с массивом байтов, беззнаковых байтов, коротких целых чисел, коротких целых без знака, целых чисел, беззнаковых целых чисел или чисел с плавающей запятой. Аргумент type может также принимать значение GL_2_BYTES, GL_3_BYTES или GL_4_BYTES. В этом случае цепочки длиной в 2, 3 или 4 байта читаются из массива lists, а результирующее смещение вычисляется по следующему алгоритму (здесь byte[0] — стартовый байт последовательности):

/* b = 2, 3 или 4; байты массива нумеруются в последовательности 0, 1, 2, 3 */
offset = 0;

for ( i = 0; i < b; i++ )
{
   offset = offset << 8;
   offset += byte[ i ];
}

index = offset + listbase;

Для многобайтовых полей первыми обрабатываются старшие разряды, а в остальном порядок идентичен обработке массива.

Листинг 7.4 приведен нами в качестве примера использования комбинированных списков отображения, это фрагмент программы, полностью представленной в листинге 7.5. Эта программа рисует символы векторного шрифта (набора символов, образованных отрезками линий). Процедура initStrokedFont() устанавливает индексы списка отображения для каждого символа так, чтобы они соответствовали своим ASCII-кодам.

Листинг 7.4. Определение составных списков отображений
void initStrokedFont( void )
{
   GLuint base;
   base = glGenLists( 128 );
   glListBase( base );
   glNewList( base + 'A', GL_COMPILE );
   drawLetter( Adata );
   glEndList();
   glNewList( base + 'E', GL_COMPILE );
   drawLetter( Edata );
   glEndList();
   glNewList( base + 'P', GL_COMPILE );
   drawLetter( Pdata );
   glEndList();
   glNewList( base + 'R', GL_COMPILE );
   drawLetter( Rdata );
   glEndList();
   glNewList( base + 'S', GL_COMPILE );
   drawLetter( Sdata );
   glEndList();
   glNewList( base + ' ', GL_COMPILE ); /* ?????? */
   glTranslatef( 8.0, 0.0, 0.0 );
   glEndList();
}

Команда glGenLists() заказывает 128 последовательных индексов. Первый из них становится базовым значением. В списке отображения каждый символ представляется суммой базового значения и ASCII-кода символа. В этом примере мы описываем только пять букв и пробел.

После создания списков отображения можно вызвать команду glCallLists() для их обработки. Так, можно передать процедуре printStrokedString() строку символов:

void printStrokedString( GLbyte *s )
{
   GLint len = strlen( s );
   glCallLists( len, GL_BYTE, s );
}

Здесь значение ASCII-кода служит как смещение в наборе индексов, а целевой индекс образуется его сложением с текущим базовым смещением. Результат работы программы, представленной в листинге 7.5, показан на рис. 7.1.


Рис. 7.1. Фраза, составленная из символов векторного шрифта: A, E, P, R, S

Листинг 7.5. Составные списки отображения, определяющие векторный шрифт:
//stroke.c
#define PT 1
#define STROKE 2
#define END 3

typedef struct charpoint
{
   GLfloat x, y;
   int type;
}

CP;
CP Adata[] = {
                { 0, 0, PT}, {0, 9, PT}, {1, 10, PT}, {4, 10, PT},
                {5, 9, PT}, {5, 0, STROKE}, {0, 5, PT}, {5, 5, END}
             };
CP Edata[] = {
                {5, 0, PT}, {0, 0, PT}, {0, 10, PT}, {5, 10, STROKE},
                {0, 5, PT}, {4, 5, END}
             };
CP Pdata[] = {
                {0, 0, PT}, {0, 10, PT}, {4, 10, PT}, {5, 9, PT}, {5, 6, PT},
                {4, 5, PT}, {0, 5, END}
             };
CP Rdata[] = {
                {0, 0, PT}, {0, 10, PT}, {4, 10, PT}, {5, 9, PT}, {5, 6, PT},
                {4, 5, PT}, {0, 5, STROKE}, {3, 5, PT}, {5, 0, END}
             };
CP Sdata[] = {
                {0, 1, PT}, {1, 0, PT}, {4, 0, PT}, {5, 1, PT}, {5, 4, PT},
                {4, 5, PT}, {1, 5, PT}, {0, 6, PT}, {0, 9, PT}, {1, 10, PT},
                {4, 10, PT}, {5, 9, END}
             };
/* drawLetter() интерпретирует команды из массива
* для символа и воспроизводит символ отрезками прямых
*/
static void drawLetter( CP *l )
{
   glBegin( GL_LINE_STRIP );

   while ( 1 )
   {
      switch ( l->type )
      {

         case PT:
            glVertex2fv( &l->x );
            break;

         case STROKE:
            glVertex2fv( &l->x );
            glEnd();
            glBegin( GL_LINE_STRIP );
            break;

         case END:
            glVertex2fv( &l->x );
            glEnd();
            glTranslatef( 8.0, 0.0, 0.0 );
            return ;
      }

      l++;
   }
}

/* Создание списка отображения для каждого из 6 символов */
static void init( void )
{
   GLuint base;
   glShadeModel( GL_FLAT );
   base = glGenLists( 128 );
   glListBase( base );
   glNewList( base + 'A', GL_COMPILE );
   drawLetter( Adata );
   glEndList();
   glNewList( base + 'E', GL_COMPILE );
   drawLetter( Edata );
   glEndList();
   glNewList( base + 'P', GL_COMPILE );
   drawLetter( Pdata );
   glEndList();
   glNewList( base + 'R', GL_COMPILE );
   drawLetter( Rdata );
   glEndList();
   glNewList( base + 'S', GL_COMPILE );
   drawLetter( Sdata );
   glEndList();
   glNewList( base + ' ', GL_COMPILE );
   glTranslatef( 8.0, 0.0, 0.0 );
   glEndList();
}

char *test1 = "A SPARE SERAPE APPEARS AS";
char *test2 = "APES PREPARE RARE PEPPERS";
static void printStrokedString( char *s )
{
   GLsizei len = strlen( s );
   glCallLists( len, GL_BYTE, ( GLbyte * ) s );
}

void display( void )
{
   glClear( GL_COLOR_BUFFER_BIT );
   glColor3f( 1.0, 1.0, 1.0 );
   glPushMatrix();
   glScalef( 2.0, 2.0, 2.0 );
   glTranslatef( 10.0, 30.0, 0.0 );
   printStrokedString( test1 );
   glPopMatrix();
   glPushMatrix();
   glScalef( 2.0, 2.0, 2.0 );
   glTranslatef( 10.0, 13.0, 0.0 );
   printStrokedString( test2 );
   glPopMatrix();
   glFlush();
}

void reshape( int w, int h )
{
   glViewport( 0, 0, ( GLsizei ) w, ( GLsizei ) h );
   glMatrixMode( GL_PROJECTION );
   glLoadIdentity();
   gluOrtho2D( 0.0, ( GLdouble ) w, 0.0, ( GLdouble ) h );
}

void keyboard( unsigned char key, int x, int y )
{
   switch ( key )
   {

      case ' ':
         glutPostRedisplay();
         break;

      case 27:
         exit( 0 );
         break;
   }
}

int main( int argc, char** argv )
{
   glutInit( &argc, argv );
   glutInitDisplayMode( GLUT_SINGLE | GLUT_RGB );
   glutInitWindowSize( 440, 120 );
   glutCreateWindow( argv[ 0 ] );
   init();
   glutReshapeFunc( reshape );
   glutKeyboardFunc( keyboard );
   glutDisplayFunc( display );
   glutMainLoop();
   return 0;
}

Управление параметрами состояния с помощью списков отображения

Список отображения может содержать вызовы команд, которые изменяют переменные состояния OpenGL. Эти изменения производятся во время выполнения списка отображения, как если бы команды выполнялись в непосредственном режиме, и их результаты остаются зафиксированными по окончании выполнения списка отображения. Вы уже видели это в листинге 7.2, и мы снова демонстрируем в листинге 7.6, что изменения текущих цвета и матрицы остаются в силе и после выполнения списка отображения.

Листинг 7.6. Сохранение состояния после выполнения списка отображения
glNewList(listIndex,GL_COMPILE);
glColor3f(1.0, 0.0, 0.0);
glBegin(GL_POLYGON);
glVertex2f(0.0, 0.0);
glVertex2f(1.0, 0.0);
glVertex2f(0.0, 1.0);
glEnd();
glTranslatef(1.5, 0.0, 0.0);
glEndList();

Если теперь вы вызовете следующую последовательность команд, линия будет нарисована красным (текущим) цветом и дополнительно сместится на значение (1.5, 0.0, 0.0):

glCallList(listIndex);
glBegin(GL_LINES);
glVertex2f(2.0,-1.0);
glVertex2f(1.0, 0.0);
glEnd();

С переменными состояния обращаются по-разному. Можно либо оставить изменения в силе, либо, наоборот, предварительно перед обработкой списка зафиксировать состояние и восстановить его по окончании выполнения списка. Это ваше право, но помните, что команда glGet*() в списке отображения недоступна и, таким образом внутри него нет возможности запросить текущие переменные состояния.

В то же время в вашем распоряжении имеется команда glPushAttrib(), позволяющая сохранить разом несколько переменных состояния, и команда glPopAttrib() — для восстановления значений. Чтобы сохранить и восстановить текущую матрицу, используйте команды glPushMatrix() и glPopMatrix() согласно рекомендациям раздела “Операции со стеком матрицы” главы 3. Эти команды могут легально кэшироваться в списке отображения. Чтобы восстановить значения переменных состояния из листинга 7.6, мы могли бы воспользоваться кодом из листинга 7.7.

Листинг 7.7. Восстановление значений переменных состояния изнутри списка отображения
glNewList(listIndex,GL_COMPILE);
glPushMatrix();
glPushAttrib(GL_CURRENT_BIT);
glColor3f(1.0, 0.0, 0.0);
glBegin(GL_POLYGON);
glVertex2f(0.0, 0.0);
glVertex2f(1.0, 0.0);
glVertex2f(0.0, 1.0);
glEnd();
glTranslatef(1.5, 0.0, 0.0);
glPopAttrib();
glPopMatrix();
glEndList();

Если вы прибегнете к варианту листинга 7.7, с помощью кода листинга 7.8 вы сможете нарисовать линию зеленого цвета, в прежней позиции. Если вы воспользуетесь списком отображения из листинга 7.6, то есть не сохраните (и не восстановите) состояние, линия будет красной, а ее позиция каждый из десяти раз будет смещаться на значение (1.5, 0.0, 0.0).

Листинг 7.8. Пример того, как список отображения может или не может воздействовать на drawLine()
void display( void )
{
   GLint i;
   glClear( GL_COLOR_BUFFER_BIT );
   glColor3f( 0.0, 1.0, 0.0 ); /* установить текущим цветом зеленый */

   for ( i = 0; i < 10; i++ )
      glCallList( listIndex ); /* список отображения выполняется 10 раз */

   drawLine(); /* где появляется линия и каким цвето она окрашена? */

   glFlush();
}

Инкапсуляция изменений режима

Вы можете использовать списки отображения для организации и сохранения групп команд, изменяющих атрибуты режимов, или для варьирования параметров. При переключении с одной группы настроек на другую использование списков отображения бывает более эффективно, чем непосредственные вызовы, благодаря кэшированию настроек в формате, наиболее подходящем вашей графи ческой системе.

Это случаи варьирования освещения, модели распространения света и свойств материала. Вы можете также использовать списки отображения в качестве шаблонов штриховки, параметров тумана и для обсчета секущих плоскостей. В этих случаях вы увидите, что списки отображения близки по скорости выполнения к эквивалентным непосредственным вызовам, но помните, что спискам отображений присущи дополнительные издержки на само их поддержание при выполнении. Листинг 7.9 показывает, как использовать списки отбражения для переключения между тремя различными шаблонами штриховки. Сначала вызывается glGenLists() для выделения индексов для каждого образца, и затем для всех трех создаются списки отображения. В результате этой подготовки можно использовать команду glCallList() для переключения с одного шаблона на другой.

Листинг 7.9. Списки отображения для изменения режима
GLuint offset;
offset = glGenLists(3);
glNewList(offset, GL_COMPILE);
glDisable(GL_LINE_STIPPLE);
glEndList();
glNewList(offset+1, GL_COMPILE);
glEnable(GL_LINE_STIPPLE);
glLineStipple(1, 0x0F0F);
glEndList();
glNewList(offset+2, GL_COMPILE);
glEnable(GL_LINE_STIPPLE);
glLineStipple(1, 0x1111);
glEndList();
#define drawOneLine(x1,y1,x2,y2) glBegin(GL_LINES); \
glVertex2f((x1),(y1)); glVertex2f((x2),(y2)); glEnd();
glCallList(offset);
drawOneLine(50.0, 125.0, 350.0, 125.0);
glCallList(offset+1);
drawOneLine(50.0, 100.0, 350.0, 100.0);
glCallList(offset+2);
drawOneLine(50.0, 75.0, 350.0, 75.0);


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
    Сообщений 0    Оценка 0        Оценить