Методы наиболее успешной практики для работы с данными вершины

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

  Наборы данных Вершины рисунка 10-1 могут быть довольно большими
Vertex data sets can be quite large

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

Поймите как потоки данных вершины через OpenGL

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

  Канал передачи данных Вершины рисунка 10-2
Vertex data path

Рисунок 10-3 бросает более внимательный взгляд на канал передачи данных вершины при использовании непосредственного режима. Без любой оптимизации Ваши данные вершины могут быть скопированы в различных точках в канале передачи данных. Если Ваше приложение использует непосредственный режим для каждой вершины отдельно, вызовы к OpenGL сначала изменяют текущую вершину, копирующуюся в буфер команд каждый раз, когда Ваше приложение делает a glVertex* вызвать. Это не только дорого с точки зрения операций копии, но также и в функции наверху для указания каждой вершины.

  Режим Immediate рисунка 10-3 требует копии текущих данных вершины
Immediate mode requires a copy of the current vertex data

Команды OpenGL glDrawRangeElements, glDrawElements, и glDrawArrays представьте многократные геометрические примитивы от данных массива, с помощью очень немногих вызовов подпрограммы. Перечисление 10-1 показывает типичную реализацию. Ваше приложение создает структуру вершины, содержащую все элементы для каждой вершины. Для каждого элемента Вы включаете клиентский массив и обеспечиваете указатель и смещаете к OpenGL так, чтобы это знало, как найти те элементы.

Перечисление 10-1  , Представляющее использование данных вершины glDrawElements.

typedef struct _vertexStruct
{
    GLfloat position[2];
    GLubyte color[4];
} vertexStruct;
 
void DrawGeometry()
{
    const vertexStruct vertices[] = {...};
    const GLubyte indices[] = {...};
 
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(2, GL_FLOAT, sizeof(vertexStruct), &vertices[0].position);
    glEnableClientState(GL_COLOR_ARRAY);
    glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(vertexStruct), &vertices[0].color);
 
    glDrawElements(GL_TRIANGLE_STRIP, sizeof(indices)/sizeof(GLubyte), GL_UNSIGNED_BYTE, indices);
}

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

Методы для обработки данных вершины

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

Буферы вершины

Буферы вершины доступны, поскольку базовая функция, запускающаяся в OpenGL 1.5, и на более ранних версиях OpenGL через буфер вершины, возражает расширению (GL_ARB_vertex_buffer_object). Буферы вершины используются для улучшения пропускной способности статических или динамических данных вершины в приложении.

Буферный объект является блоком памяти, принадлежавшей OpenGL. Ваше приложение читает из или пишет в буфер с помощью вызовов OpenGL такой как glBufferData, glBufferSubData, и glGetBufferSubData. Ваше приложение может также получить указатель на эту память, работа, называемая отображением буфера. OpenGL предотвращает Ваше приложение и его от одновременного использования данных, хранивших в буфере. Когда Ваше приложение отображает буфер или пытается изменить его, OpenGL может блокировать, пока предыдущие команды рисования не завершились.

Используя буферы вершины

Можно установить и использовать буферы вершины путем выполнения этих шагов:

  1. Вызовите функцию glGenBuffers создать новое имя для буферного объекта.

    void glGenBuffers(sizei n, uint *buffers );

    n число буферов, для которых Вы хотите создать идентификаторы.

    buffers указывает указатель на память для хранения буферных имен.

  2. Вызовите функцию glBindBuffer связывать неиспользованное имя к буферному объекту. После этого вызова недавно создаваемый буферный объект инициализируется с буфером памяти нулевого размера и состояния по умолчанию. (Для настройки по умолчанию посмотрите спецификацию OpenGL для ARB_vertex_buffer_object.)

    void glBindBuffer(GLenum target, GLuint buffer);

    target должен быть установлен в GL_ARRAY_BUFFER.

    buffer указывает уникальное имя для буферного объекта.

  3. Заполните буферный объект путем вызывания функции glBufferData. По существу этот вызов загружает Ваши данные на GPU.

    void glBufferData(GLenum target, sizeiptr size,
                const GLvoid *data, GLenum usage);

    target должен быть установлен в GL_ARRAY_BUFFER.

    size указывает размер хранилища данных.

    *data точки к исходным данным. Если это не NULL, исходные данные копируются в данные, хранившие буферного объекта. Если NULL, содержание хранилища данных не определено.

    usage константа, обеспечивающая подсказку относительно того, как Ваше приложение планирует использовать данные, хранившие в буферном объекте. Эти примеры использование GL_STREAM_DRAW, который указывает, что приложение планирует и изменить и нарисовать использование буфера, и GL_STATIC_DRAW, который указывает, что приложение будет определять данные один раз, но использовать их для рисования много раз. Для получения дополнительной информации на буферных подсказках, посмотрите Буферные Подсказки Использования

  4. Включите массив вершины путем вызова glEnableClientState и предоставление GL_VERTEX_ARRAY постоянный.

  5. Укажите на содержание буферного объекта вершины путем вызывания функции такой как glVertexPointer. Вместо того, чтобы обеспечить указатель, Вы обеспечиваете смещение в буферный объект вершины.

  6. Обновить данные в буферном объекте, Ваших вызовах приложения glMapBuffer. Отображение буфера препятствует тому, чтобы GPU воздействовал на данные и дает Вашему приложению подсказку к памяти, которую это может использовать для обновления буфера.

    void *glMapBuffer(GLenum target, GLenum access);

    target должен быть установлен в GL_ARRAY_BUFFER.

    access указывает операции, которые Вы планируете выполнить на данных. Можно предоставить READ_ONLY, WRITE_ONLY, или READ_WRITE.

  7. Запишите пиксельные данные в указатель, полученный от вызова до glMapBuffer.

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

Перечисление 10-2 показывает код, использующий буферное расширение объекта вершины для динамических данных. Этот пример перезаписывает все данные вершины во время каждой работы получения.

Перечисление 10-2  Используя буфер вершины возражает расширению с динамическими данными

//  To set up the vertex buffer object extension
#define BUFFER_OFFSET(i) ((char*)NULL + (i))
glBindBuffer(GL_ARRAY_BUFFER, myBufferName);
 
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, stride, BUFFER_OFFSET(0));
 
//  When you want to draw using the vertex data
draw_loop {
    glBufferData(GL_ARRAY_BUFFER, bufferSize, NULL, GL_STREAM_DRAW);
    my_vertex_pointer = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
    GenerateMyDynamicVertexData(my_vertex_pointer);
    glUnmapBuffer(GL_ARRAY_BUFFER);
    PerformDrawing();
}

Перечисление 10-3 показывает код, использующий буферное расширение объекта вершины со статическими данными.

Перечисление 10-3  Используя буфер вершины возражает расширению со статическими данными

//  To set up the vertex buffer object extension
#define BUFFER_OFFSET(i) ((char*)NULL + (i))
glBindBuffer(GL_ARRAY_BUFFER, myBufferName);
glBufferData(GL_ARRAY_BUFFER, bufferSize, NULL, GL_STATIC_DRAW);
GLvoid* my_vertex_pointer = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
GenerateMyStaticVertexData(my_vertex_pointer);
glUnmapBuffer(GL_ARRAY_BUFFER);
 
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, stride, BUFFER_OFFSET(0));
 
//  When you want to draw using the vertex data
draw_loop {
    PerformDrawing();
}

Буферные подсказки использования

Главное преимущество буферных объектов - то, что приложение может предоставить информацию о том, как это использует данные, хранившие в каждом буфере. Например, Перечисление 10-2 и Перечисление 10-3 дифференцировались между случаями, где данные, как ожидали, никогда не изменятся (GL_STATIC_DRAW) и случаи, где буферные данные могли бы измениться (GL_DYNAMIC_DRAW). Параметр использования позволяет средству рендеринга OpenGL изменять свою стратегию выделения буфера вершины для улучшения производительности. Например, в то время как динамические буферы могут быть сохранены в оперативной памяти и получены GPU через DMA, статические буферы могут быть выделены непосредственно в памяти GPU.

Если OpenGL необходимо ограничить подсказки использования одним из трех случаев использования, совместимость ES полезна для Вас:

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

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

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

Другие константы использования детализированы в буферной спецификации вершины.

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

  Геометрия перечисления 10-4 с различными образцами использования

typedef struct _vertexStatic
{
    GLfloat position[2];
} vertexStatic;
 
typedef struct _vertexDynamic
{
    GLubyte color[4];
} vertexDynamic;
 
// Separate buffers for static and dynamic data.
GLuint    staticBuffer;
GLuint    dynamicBuffer;
GLuint    indexBuffer;
 
const vertexStatic staticVertexData[] = {...};
vertexDynamic dynamicVertexData[] = {...};
const GLubyte indices[] = {...};
 
void CreateBuffers()
{
    glGenBuffers(1, &staticBuffer);
    glGenBuffers(1, &dynamicBuffer);
    glGenBuffers(1, &indexBuffer);
 
// Static position data
    glBindBuffer(GL_ARRAY_BUFFER, staticBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(staticVertexData), staticVertexData, GL_STATIC_DRAW);
 
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
 
// Dynamic color data
// While not shown here, the expectation is that the data in this buffer changes between frames.
    glBindBuffer(GL_ARRAY_BUFFER, dynamicBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(dynamicVertexData), dynamicVertexData, GL_DYNAMIC_DRAW);
}
 
void DrawUsingVertexBuffers()
{
    glBindBuffer(GL_ARRAY_BUFFER, staticBuffer);
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(2, GL_FLOAT, sizeof(vertexStatic), (void*)offsetof(vertexStatic,position));
    glBindBuffer(GL_ARRAY_BUFFER, dynamicBuffer);
    glEnableClientState(GL_COLOR_ARRAY);
    glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(vertexDynamic), (void*)offsetof(vertexDynamic,color));
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
    glDrawElements(GL_TRIANGLE_STRIP, sizeof(indices)/sizeof(GLubyte), GL_UNSIGNED_BYTE, (void*)0);
}

Буферное расширение диапазона сброса

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

Для использования буферного расширения диапазона сброса выполните эти шаги:

  1. Включите буферное расширение сброса путем вызова glBufferParameteriAPPLE.

    glBufferParameteriAPPLE(GL_ARRAY_BUFFER,GL_BUFFER_FLUSHING_UNMAP_APPLE, GL_FALSE);

    Это отключает нормальное поведение сбрасывания OpenGL.

  2. Прежде чем Вы не отобразите буфер, необходимо вызвать glFlushMappedBufferRangeAPPLE для каждого диапазона буфера, измененного приложением.

    void glFlushMappedBufferRangeAPPLE(enum target, intptr offset, sizeiptr size);

    target тип изменяемого буфера; для данных вершины это ARRAY_BUFFER.

    offset смещение в буфер для измененных данных.

    size длина измененных данных в байтах.

  3. Вызвать glUnmapBuffer. OpenGL не отображает буфер, но это требуется, чтобы обновлять только части буфера Ваше приложение, явно отмеченное, как изменено.

Для получения дополнительной информации посмотрите спецификацию APPLE_flush_buffer_range.

Расширение диапазона массива вершины

Расширение диапазона массива вершины (APPLE_vertex_array_range) позволяет Вам определить область памяти для Ваших данных вершины. Драйвер OpenGL может оптимизировать использование памяти путем создания единственной памяти, отображающейся для данных вершины. Можно также обеспечить подсказку относительно того, как должны храниться данные: кэшируемый или совместно использованный. Кэшируемая опция указывает для кэширования данных вершины в видеопамяти. Совместно используемая опция указывает, что данные должны быть отображены в область памяти, позволяющей GPU получать доступ к данным вершины непосредственно с помощью передачи DMA. Эта опция является лучшей для динамических данных. При использовании общей памяти Вам будут нужны к двойному буферу Ваши данные.

Можно установить и использовать расширение диапазона массива вершины путем выполнения этих шагов:

  1. Включите расширение путем вызова glEnableClientState и предоставление GL_VERTEX_ARRAY_RANGE_APPLE постоянный.

  2. Выделите хранение для данных вершины. Вы ответственны за поддержание хранения для данных.

  3. Определите массив данных вершины путем вызывания функции такой как glVertexPointer. Необходимо предоставить указатель на данные.

  4. Дополнительно установите подсказку об обработке хранения данных массива путем вызывания функции glVertexArrayParameteriAPPLE.

    GLvoid glVertexArrayParameteriAPPLE(GLenum pname, GLint param);

    pname должен быть VERTEX_ARRAY_STORAGE_HINT_APPLE.

    param подсказка, указывающая, как Ваше приложение ожидает использовать данные. OpenGL использует эту подсказку для оптимизации производительности. Можно предоставить также STORAGE_SHARED_APPLE или STORAGE_CACHED_APPLE. Значение по умолчанию STORAGE_SHARED_APPLE, который указывает, что данные вершины являются динамичными и что OpenGL должен использовать методы оптимизации и сбрасывания, подходящие для этого вида данных. Если Вы ожидаете, что данные, которыми снабжают, будут статичны, использовать STORAGE_CACHED_APPLE так, чтобы OpenGL мог оптимизировать соответственно.

  5. Вызовите функцию OpenGL glVertexArrayRangeAPPLE установить набор данных.

    void glVertexArrayRangeAPPLE(GLsizei length, GLvoid *pointer);

    length указывает длину диапазона массива вершины. Длина обычно является числом байтов без знака.

    *pointer точки к основе вершины выстраивают диапазон.

  6. Нарисуйте с данными вершины с помощью стандартных команд массива вершины OpenGL.

  7. Если необходимо изменить данные вершины, установить объект предела после представления всех команд рисования. Посмотрите Пределы Использования для Синхронизации с более прекрасными зернами

  8. Выполните другую работу так, чтобы GPU имел время для обработки команд рисования, использующих массив вершины.

  9. Вызвать glFinishFenceAPPLE получить доступ к массиву вершины.

  10. Измените данные в массиве вершины.

  11. Вызвать glFlushVertexArrayRangeAPPLE.

    void glFlushVertexArrayRangeAPPLE(GLsizei length, GLvoid *pointer);

    length указывает длину диапазона массива вершины, в байтах.

    *pointer точки к основе вершины выстраивают диапазон.

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

Перечисление 10-5 показывает код, устанавливающий и использующий расширение диапазона массива вершины с динамическими данными. Это перезаписывает все данные вершины во время каждой итерации через цикл получения. Вызов к glFinishFenceAPPLE команда гарантирует, что CPU и GPU не получают доступ к данным одновременно. Несмотря на то, что этот пример вызывает glFinishFenceAPPLE функционируйте почти сразу после установки предела, в действительности необходимо разделить эти вызовы для разрешения параллельной работы GPU и CPU. Чтобы видеть, как это сделано, считайте Двойную буферизацию Использования для Предотвращения Конфликтов Ресурса.

Перечисление 10-5  Используя вершину выстраивает расширение диапазона с динамическими данными

//  To set up the vertex array range extension
glVertexArrayParameteriAPPLE(GL_VERTEX_ARRAY_STORAGE_HINT_APPLE, GL_STORAGE_SHARED_APPLE);
glVertexArrayRangeAPPLE(buffer_size, my_vertex_pointer);
glEnableClientState(GL_VERTEX_ARRAY_RANGE_APPLE);
 
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, my_vertex_pointer);
glSetFenceAPPLE(my_fence);
 
//  When you want to draw using the vertex data
draw_loop {
    glFinishFenceAPPLE(my_fence);
    GenerateMyDynamicVertexData(my_vertex_pointer);
    glFlushVertexArrayRangeAPPLE(buffer_size, my_vertex_pointer);
    PerformDrawing();
    glSetFenceAPPLE(my_fence);
}

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

Перечисление 10-6  Используя вершину выстраивает расширение диапазона со статическими данными

//  To set up the vertex array range extension
GenerateMyStaticVertexData(my_vertex_pointer);
glVertexArrayParameteriAPPLE(GL_VERTEX_ARRAY_STORAGE_HINT_APPLE, GL_STORAGE_CACHED_APPLE);
glVertexArrayRangeAPPLE(array_size, my_vertex_pointer);
glEnableClientState(GL_VERTEX_ARRAY_RANGE_APPLE);
 
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, stride, my_vertex_pointer);
 
//  When you want to draw using the vertex data
draw_loop {
    PerformDrawing();
}

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

Объект массива вершины

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

Для использования этого расширения выполните эти шаги во время подпрограмм инициализации приложения:

  1. Генерируйте объект массива вершины для конфигурации указателей, которые Вы хотите использовать вместе.

    void glGenVertexArraysAPPLE(sizei n, const uint *arrays);

    n число массивов, для которых Вы хотите создать идентификаторы.

    arrays указывает указатель на память для хранения имен массива.

    glGenVertexArraysAPPLE(1,&myArrayObject);
  2. Свяжите объект массива вершины, который что Вы хотите сконфигурировать.

    void glBindVertexArrayAPPLE(uint array);

    array идентификатор для массива, из которого Вы получили glGenVertexArraysAPPLE.

    glBindVertexArrayAPPLE(myArrayObject);
  3. Вызовите подпрограммы указателя (glColorPointer и т.д.), что Вы обычно вызывали бы в своем цикле рендеринга. Когда объект массива вершины связывается, эти вызовы изменяют в настоящее время связанный объект массива вершины вместо состояния OpenGL по умолчанию.

        glBindBuffer(GL_ARRAY_BUFFER, staticBuffer);
        glEnableClientState(GL_VERTEX_ARRAY);
        glVertexPointer(2, GL_FLOAT, sizeof(vertexStatic), (void*)offsetof(vertexStatic,position));
    ...
  4. Повторите предыдущие шаги для каждой конфигурации указателей вершины.

  5. В Вашем цикле рендеринга замените вызовы для конфигурирования указателей массива с вызовом для привязки объекта массива вершины.

    glBindVertexArrayAPPLE(myArrayObject);
    glDrawArrays(...);
  6. Если необходимо возвратиться к поведению OpenGL по умолчанию, вызвать glBindVertexArrayAPPLE и передача в 0.

    glBindVertexArrayAPPLE(0);