Вивчаємо можливості OpenGL
У статті глибше описано структуру API OpenGL, обробку помилок, роботу з розширеннями та отримання інформації про можливості драйвера.
Зміст
Структура API OpenGL
API OpenGL описаний мовою C без застосування C++ заради простоти та платформонезалежності. Він складається тільки з функцій, констант і примітивних типів, оголошених через typedef, таких як "typedef int GLenum;" .
Функції поділяються на дві групи:
- команди (англ. commands) зміни стану драйвера
- запити (англ. queries) стану драйвера
Ось кілька прикладів:
Обробка помилок
OpenGL старанно обробляє помилки, такі як неприпустимий аргумент, неправильна константа enum, несвоєчасний виклик команди. Дізнатися про загальну помилку в одній з попередніх функцій-команд можна функцією-запитом GLenum glGetError() .
Умовно, код може виглядати так:
Функцію можна покращити, якщо врахувати таке:
- Роздрукувати рядок помилки можна в потік помилок std::cerr
- Будь-яку помилку можна вважати фатальною, викликаючи std::abort для аварійного завершення програми після виведення тексту помилки
- Функцію можна зробити статичним методом класу CUtils
Представимо покращену версію:
Після додавання цього методу можна покращити основний цикл програми:
Розширення OpenGL
З метою максимальної гнучкості, всі зміни OpenGL вносяться у вигляді розширень. Розширення OpenGL – це задокументована специфікація, яка описує нові функції та їхню поведінку, зміни у поведінці старих функцій та нові константи. Кожне розширення має назву, наприклад, "GL_ARB_multitexture" . При випуску нової версії OpenGL частина розширень потрапляє у нову версію тастає частиною ядра OpenGL. Таким чином, у версії OpenGL 3.0 і вище ви автоматично отримуєте ряд можливостей, які OpenGL 1.2 були доступні тільки як розширення.
Функція, описана в розширенні, може не існувати в конкретній реалізації OpenGL (якщо вона не підтримує це розширення). Тому програміст повинен
Отримання інформації про версію OpenGL
- константа з ім'ям GL_VERSION повертає рядок версії OpenGL, причому на початку рядка обов'язково стоїть ".", а решта рядка не визначена. Наприклад, рядок "3.0 Mesa 10.3.2" оглядає OpenGL версії 3.0, реалізований підсистемою графіки Mesa версії 10.3.2.
- константа під назвою GL_VENDOR повертає ім'я постачальника реалізації OpenGL. Наприклад, рядок "Intel Open Source Technology Center" означає "Відеодрайвер наданий OpenSource-підрозділом корпорації Intel".
- константа з ім'ям GL_EXTENSIONS містить повний перелік розширень, розділений пробілами. Список зазвичай налічує понад сто розширень.
Функція друку інформації про контекст
Наслідуючи "правилу трьох ударів", можна відрефакторити цей код:
Бібліотека GLEW
- Сайт проекту: http://glew.sourceforge.net/
- Debian/Ubuntu доступна в пакеті libglew-dev
- ви просто викликаєте функції на ім'я; якщо функції немає, відбудеться розйменування нульової вказівки
- також можна використовувати модифіковане ім'я розширення (з префіксом “GLEW_” замість “GL_”) як цілочисленну змінну зі значенням 0 або 1; 1 означає, що розширення є і доступне, 0 означає, що розширення немає або воно недоступне
- якщо розширення недоступне, ви не повинні викликати функції розширення, щоб не отримати розименування нульового покажчика
- якщо при створенні контексту OpenGL ви зажадали і отримали контекст не нижче за певну версію, то можна навіть не перевіряти розширення, що увійшли до цієї версії: вони є.
Підключати заголовок glew.h слід до першого включення gl.h, інакше ви отримаєте помилку під час компіляції.
Бібліотека GLEW вимагає явного виклику функції glewInit для своєї ініціалізації. Здійснити виклик слід лише один раз. Щоб не накладати зайвих обмежень на клас CAbstractWindow, потрібно гарантувати, що при першому конструюванні об'єкта CAbstractWindow функція буде викликана, а при наступних — вже ні. Також потрібно встановити глобальну змінну-прапор glewExperimental , щоб GLEW обертала функції з версій OpenGL 3.x та 4.x.
Для цієї мети можна використовувати два підходи
- взяти зі стандартного заголовка функцію std::call_once
- завести у функції статичну змінну типу bool, яка встановлюватиметься в false в ініціалізаторі (який для статичних змінних усередині функції викликається рівно один раз)
У багатопотоковому середовищі було б правильним використовувати call_once, щоб унеможливити повторний виклик ініціалізації під час виконання “glewInit” в іншому потоці. Однак ні контекст OpenGL, ні GLEW не можуть використовуватися з кількох потоків одночасно. Тому call_once нам не буде потрібно, і достатньо статичної змінної типу bool:
Дізнаємося про розширення через GLEW
Читати повний перелік розширень, отриманий через glGetString(GL_EXTENSIONS) , не дуже комфортно. Сканувати його програмно дуже трудомістко щодо обчислень.
Для зручного отримання розширень у GLEW є змінні-прапори, які встановлюються під час виклику glewInit() . Для перевірки наявності розширення треба:
- знайти ідентифікатор розширення у реєстрі розширень (opengl.org), наприклад, GL_ARB_vertex_shader
- замінити префікс GL_ на GLEW_
- написати перевірку змінної-прапора з таким ім'ям
Тепер можна покращити функцію PrintOpenGLInfo:
Створюємо працездатний додаток
Код запиту версії OpenGL розмістимо в класі CWindow, тому що в подальших прикладах нам вже не потрібно буде друкувати щось у консоль.