Хабрахабр

Суперсовременный OpenGL. Часть 1

Все кто хоть немного разбирался в теме OpenGL знают, что существует большое количество статей и курсов по этой теме, но многие не затрагивают современный API, а часть из них вообще рассказывают про glBegin и glEnd. Всем привет. Прошу простить меня за мою плохую грамматику (буду благодарен за исправления). Я постараюсь охватить некоторые нюансы нового API начиная с 4-й версии.
На этот раз я попробую написать интересную и познавательную статью, а что получилось — решать добрым хабравчанинам.

Если вам понравится, напишу про оптимизацию OpenGL и уменьшение DrawCall'ов.

Приступим!

Что будет в этой статье — функционал современного OpenGL
Чего не будет в этой статье — современные подходы к рендерингу на OpenGL

Содержание:

  • Direct State Access
  • Debug
  • Separate Shader Objects
  • Texture arrays
  • Texture view
  • Single buffer for index and vertex
  • Tessellation and compute shading
  • Path rendering

DSA (Direct State Access)

Direct State Access — Прямой доступ к состоянию. Средство изменения объектов OpenGL без необходимости привязывать их к контексту. Это позволяет изменять состояние объекта в локальном контексте, не затрагивая глобальное состояние, разделяемое всеми частями приложения. Это также делает API-интерфейс немного более объектно-ориентированным, поскольку функции, которые изменяют состояние объектов, могут быть четко определены. Вот что нам говорит OpenGL Wiki.

Как мы знаем, OpenGL — это API-интерфейс с множеством переключателей — glActiveTexture, glBindTexture и т.д.

Отсюда у нас возникают некоторые проблемы:

  • Селектор и текущие состояния могут вносить более глубокое изменение состояния
  • Может потребоваться привязать / изменить активный юнит, чтобы установить флильтр для текстур
  • Управление состоянием становится проблематичным в следствии чего растет сложность приложения
  • Неизвестное состояние приводит к дополнительным настройкам
  • Попытки сохранить/восстановить состояние могут быть проблематичны

Что же предложили нам Khronos group и как же помогает DSA?

  • Добавляет функции, которые работают непосредственно с объектом / объектами
  • Устанавливает фильтр текстуры для указанного объекта текстуры, а не текущего
  • Привязывает текстуру к конкретному юниту, а не к активному
  • Добавляет очень большое количество новых функций
  • Покрывает вещи вплоть до OpenGL 1.x
  • Добавляет дополнительные функции

В теории DSA может помочь свести количество операций не относящихся к отрисовки и меняющих состояние к нулю… Но это не точно.

Теперь я вкратце пробегусь по некоторым новым функциям, подробно останавливаться на параметрах не буду, оставлю линки на вики.

  • glCreateTextures заменяет glGenTextures + glBindTexture(инициализация).
    Было:

    glGenTextures(1, &name);
    glBindTexture(GL_TEXTURE_2D, name);

    Стало:

    glCreateTextures(GL_TEXTURE_2D, 1, &name);

  • glTextureParameterX эквивалент glTexParameterX

    glGenTextures(1, &name);
    glBindTexture(GL_TEXTURE_2D, name); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);

    Теперь же мы это напишем так:

    glCreateTextures(GL_TEXTURE_2D, 1, &name); glTextureParameteri(name, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTextureParameteri(name, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTextureParameteri(name, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTextureParameteri(name, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTextureStorage2D(name, 1, GL_RGBA8, width, height);
    glTextureSubImage2D(name, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);

  • glBindTextureUnitзаменяет glActiveTexture + glBindTexture
    Вот как мы делали:

    glActiveTexture(GL_TEXTURE0 + 3);
    glBindTexture(GL_TEXTURE_2D, name);

    Теперь:

    glBindTextureUnit(3, name);

Так же изменения коснулись glTextureImage, он более не используется и вот почему:

Для ее замены была добавлена glTexStorage. glTexImage довольно небезопасная, очень легко получить невалидные текстуры, потому что функция работает без проверки при вызова, а все значения проверяются драйвером во время рисования.

Хранилище текстур решает большинство, если не все проблемы, вызываемые изменяемыми текстурами, хотя неизменяемые текстуры — более надёжно. glTexStorage предоставляет способ создания текстур с проверками, выполняемыми во время вызова, что сводит количество ошибок к минимуму.

Изменения затронули и буфер кадров:

Это не все измененные функции. Следующие на очереди — функции для буферов:
Вот список того, что сейчас входит в поддержку DSA:

  • Vertex array objects
  • Framebuffer objects
  • Program objects
  • Buffer objects
  • Matrix stacks
  • Много устаревших вещей

Debug

С версии 4.3 был добавлен новый функционал для дебага, на мой взгляд очень полезный и удобный. Теперь OpenGL будет вызывать наш callback при ошибках и дебаг сообщениях, уровень которых мы сможем настроить.

Нам надо вызвать всего две функции для включения: glEnable& glDebugMessageCallback, проще некуда.

glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(message_callback, nullptr);

Теперь напишем callback функцию для получения месседжа:

void callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, GLchar const* message, void const* user_param)
}(); auto type_str = [type]() { switch (type) { case GL_DEBUG_TYPE_ERROR: return "ERROR"; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: return "DEPRECATED_BEHAVIOR"; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: return "UNDEFINED_BEHAVIOR"; case GL_DEBUG_TYPE_PORTABILITY: return "PORTABILITY"; case GL_DEBUG_TYPE_PERFORMANCE: return "PERFORMANCE"; case GL_DEBUG_TYPE_MARKER: return "MARKER"; case GL_DEBUG_TYPE_OTHER: return "OTHER"; default: return "UNKNOWN"; } }(); auto severity_str = [severity]() { switch (severity) { case GL_DEBUG_SEVERITY_NOTIFICATION: return "NOTIFICATION"; case GL_DEBUG_SEVERITY_LOW: return "LOW"; case GL_DEBUG_SEVERITY_MEDIUM: return "MEDIUM"; case GL_DEBUG_SEVERITY_HIGH: return "HIGH"; default: return "UNKNOWN"; } }(); std::cout << source_str << ", " << type_str << ", " << severity_str << ", " << id << ": " << message << std::endl;
}

Так же мы можем настроить фильтр при помощи glDebugMessageControl. Фильтр может работать в режиме фильтрации по источнику/типу/важности или набора сообщений с использованием их идентификаторов.

Фильтр сообщений в определенном скоупе:

glPushDebugGroup( GL_DEBUG_SOURCE_APPLICATION, DEPTH_FILL_ID, 11, “Depth Fill”); //Добавляем маркер
Render_Depth_Only_Pass(); //Выполняем рендеринг
glPopDebugGroup(); //Убираем маркер

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

glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);

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

SSO (Separate Shader Objects)

Когда-то OpenGL работал как «фиксированный конвейер» — это означало, что ко всем передаваемым на визуализацию данным применялась заранее запрограммированная обработка. Следующим шагом было «програмируемый конвейер» — где программируемая часть осуществляет шейдеры, написан в GLSL, классический GLSL программа состояла из вершинного и фрагментного шейдера, но в современном OpenGL добавили некоторые новые типы шейдеров, а именно шейдеры геометрии, теселяции и расчетов (о них я расскажу в следующей части).

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


GLuint pipe = GL_NONE;
// Create shaders
GLuint fprog = glCreateShaderProgramv( GL_FRAGMENT_SHADER, 1, &text);
GLuint vprog = glCreateShaderProgramv( GL_VERTEX_SHADER, 1, &text);
// Bind pipeline
glGenProgramPipelines( 1, &pipe);
glBindProgramPipelines( pipe);
// Bind shaders
glUseProgramStages( pipe, GL_FRAGMENT_SHADER_BIT, fprog);
glUseProgramStages( pipe, GL_VERTEX_SHADER_BIT, vprog);

Как мы видим glCreateProgramPipelines генерирует дескриптор и инициализирует объект, glCreateShaderProgramv генерирует, инициализирует, компилирует и связывает шейдерную программу с использованием указанных источников, а glUseProgramStages присоединяет этапы программы к объекту конвейера. glBindProgramPipeline — связывает конвейер с контекстом.

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

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

Встроенные интерфейсы блоков определены как (из вики):
Vertex:

out gl_PerVertex
{ vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[];
};

Tesselation Control:

out gl_PerVertex
{ vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[];
} gl_out[];

Tesselation Evaluation:

out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[];
};

Geometry:

out gl_PerVertex
{ vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[];
};

Пример повторного объявления встроенного модуля и использование attribute location в обычном вершинном шейдере:

#version 450 out gl_PerVertex { vec4 gl_Position; }; layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color; layout (location = 0) out v_out
{ vec3 color;
} v_out; void main()
{ v_out.color = color; gl_Position = vec4(position, 1.0);
}

Если вам понравилось, ждите продолжение 🙂

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть