If the parameters of a function call do not match the set of parameters allowed by OpenGL, or do not interact reasonably with state that is already set in the context, then an OpenGL Error will result. The errors are presented as an error code.
For most OpenGL errors, and for most OpenGL functions, a function that emits an error will have no effect. No OpenGL state will be changed, no rendering will be initiated. It will be as if the function had not been called. There are a few cases where this is not the case.
Catching errors (the hard way)
OpenGL errors are stored in a queue until the error is actually handled.[footnotes 1] Therefore, if you do not regularly test for errors, you will not know necessarily which function call elicited a particular error. As such, error testing should be done regularly if you need to know where an error came from.
To fetch the next error in the queue (and to remove it from the queue), call this function:
If the error queue is empty, it will return GL_NO_ERROR. Otherwise, it will return one of the error enumerators below and remove that error from the queue. So to fetch all of the errors currently in the queue, you would need to loop.
A simple loop to extract the current OpenGL errors:
GLenum err; while((err = glGetError()) != GL_NO_ERROR) { // Process/log the error. }
Note: No OpenGL function call is valid when an OpenGL Context is not made current. Depending on the platform, a call to glGetError without having a context made current may crash or return any value, including indefinitely returning a valid OpenGL error code. So if you see an infinite loop in such a function, this is likely the reason why.
Meaning of errors
The glGetError function returns one of the following error codes, or GL_NO_ERROR if no (more) errors are available. Each error code represents a category of user error.
- GL_INVALID_ENUM, 0x0500
- Given when an enumeration parameter is not a legal enumeration for that function. This is given only for local problems; if the spec allows the enumeration in certain circumstances, where other parameters or state dictate those circumstances, then GL_INVALID_OPERATION is the result instead.
- GL_INVALID_VALUE, 0x0501
- Given when a value parameter is not a legal value for that function. This is only given for local problems; if the spec allows the value in certain circumstances, where other parameters or state dictate those circumstances, then GL_INVALID_OPERATION is the result instead.
- GL_INVALID_OPERATION, 0x0502
- Given when the set of state for a command is not legal for the parameters given to that command. It is also given for commands where combinations of parameters define what the legal parameters are.
- GL_STACK_OVERFLOW, 0x0503
- Given when a stack pushing operation cannot be done because it would overflow the limit of that stack’s size.
- GL_STACK_UNDERFLOW, 0x0504
- Given when a stack popping operation cannot be done because the stack is already at its lowest point.
- GL_OUT_OF_MEMORY, 0x0505
- Given when performing an operation that can allocate memory, and the memory cannot be allocated. The results of OpenGL functions that return this error are undefined; it is allowable for partial execution of an operation to happen in this circumstance.
- GL_INVALID_FRAMEBUFFER_OPERATION, 0x0506
- Given when doing anything that would attempt to read from or write/render to a framebuffer that is not complete.
- GL_CONTEXT_LOST, 0x0507 (with OpenGL 4.5 or ARB_KHR_robustness)
- Given if the OpenGL context has been lost, due to a graphics card reset.
- GL_TABLE_TOO_LARGE1, 0x8031
- Part of the ARB_imaging extension.
1: These error codes are deprecated in 3.0 and removed in 3.1 core and above.
In the OpenGL Reference documentation, most errors are listed explicitly. However, GL_OUT_OF_MEMORY and GL_CONTEXT_LOST could be generated by virtually any OpenGL function. And they can be generated for reasons not directly associated with that particular function call.
Catching errors (the easy way)
The debug output feature provides a simple method for your application to be notified via an application-defined message callback function when an OpenGL error (or other interesting event) occurs within the driver. Simply enable debug output, register a callback, and wait for it to be called with a DEBUG_TYPE_ERROR message.
This method avoids the need to sprinkle expensive and code-obfuscating glGetError() calls around your application to catch and localize the causes of OpenGL errors (and the need to conditionally compile them into debug builds to avoid the performance hit in optimized builds). The feature can even ensure that message callback functions are invoked on the same thread and within the very same call stack as the GL call that triggered the GL error (or performance warning).
A simple example showing how to utilize debug message callbacks (e.g. for detecting OpenGL errors):
void GLAPIENTRY MessageCallback( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam ) { fprintf( stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %sn", ( type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : "" ), type, severity, message ); } // During init, enable debug output glEnable ( GL_DEBUG_OUTPUT ); glDebugMessageCallback( MessageCallback, 0 );
Side effects
Under most circumstances, a function that generates an error will exit without changing any OpenGL state or initiating any OpenGL operations. They will not modify any client memory passed into those functions by the user (ie: pointer arguments). Under such errors, return values are zero or something equally innocuous. However, there are certain circumstances were an error happens and OpenGL state is modified.
Whenever the GL_OUT_OF_MEMORY error is generated, the state of the OpenGL context and/or objects is undefined.
The GL_CONTEXT_LOST error is generated (which requires OpenGL 4.5 or ARB_KHR_robustness) by any commands after the OpenGL context was lost. Those commands have no side effects, with a few special-case exceptions for functions that can block the CPU.
The multi-bind functions functions have unusual error properties. Because they aggregate the ability to bind multiple objects at once, if the binding of one object fails with an error, the others will be bound as normal. Only the erronous objects will fail to bind. Note that there is no way to detect which objects failed to bind (other than querying the context for each binding point).
No error contexts
| Core in version | 4.6 | |
|---|---|---|
| Core since version | 4.6 | |
| ARB extension | KHR_no_error |
An OpenGL Context can be created which does not report OpenGL Errors. If the context bit GL_CONTEXT_FLAG_NO_ERROR_BIT is set to true, then the context will not report most errors. It will still report GL_OUT_OF_MEMORY_ERROR where appropriate, but this can be delayed from the point where the error actually happens. No other errors will be reported.
This also means that the implementation will not check for errors either. So if you provide incorrect parameters to a function that would have provoked an error, you will get undefined behavior instead. This includes the possibility of application termination.
Contexts cannot have the no error bit and the robustsness or debug bits.
Notes
- ↑ The specification «OpenGL 4.6 (Core Profile) — October 22, 2019», chapter «2.3.1 Errors» doesn’t talk about a error queue, it talks about several flag-code pairs.
| title | description | ms.assetid | keywords | ms.topic | ms.date | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
OpenGL Error Codes |
OpenGL includes the following error codes. |
d798b677-d86d-454d-ac46-26afd82563a5 |
|
article |
05/31/2018 |
OpenGL Error Codes
OpenGL includes the following error codes.
| Error code | Description |
|---|---|
| GL_INVALID_ENUM | GLenum argument out of range. |
| GL_INVALID_VALUE | Numeric argument out of range. |
| GL_INVALID_OPERATION | Operation illegal in current state. |
| GL_STACK_OVERFLOW | Function would cause a stack overflow. |
| GL_STACK_UNDERFLOW | Function would cause a stack underflow. |
| GL_OUT_OF_MEMORY | Not enough memory left to execute function. |

В этом уроке мы познакомимся с некоторыми методами и приемами отладки вашей OpenGL-программы. Отладка в OpenGL не так сложна, и изучение некоторых приемов обязательно окупится.
Содержание
Часть 2. Базовое освещение
Часть 3. Загрузка 3D-моделей
Часть 4. Продвинутые возможности OpenGL
Часть 5. Продвинутое освещение
glGetError()
Когда вы некорректно используете OpenGL (к примеру, когда настраиваете буфер, забыв его связать (to bind)), OpenGL заметит и создаст один или несколько пользовательских флагов ошибок за кулисами. Мы можем эти ошибки отследить, вызывая функцию glGetError(), которая просто проверяет выставленные флаги ошибок и возвращает значение ошибки, если случились ошибки.
GLenum glGetError();
Эта функция возвращает флаг ошибки или вообще никакую ошибку. Список возвращаемых значений:
Внутри документации к функциям OpenGL вы можете найти коды ошибок, которые генерируются функциями, некорректно используемыми. К примеру, если вы посмотрите на документацию к функции glBindTexture(), то вы сможете найти коды ошибок, генерируемые этой функцией, в разделе «Ошибки» (Errors).
Когда флаг ошибки установлен, никаких других флагов ошибки сгенерировано не будет. Более того, когда glGetError вызывается, функция стирает все флаги ошибок (или только один на распределенной системе, см. ниже). Это значит, что если вы вызываете glGetError один раз после каждого кадра и получаете ошибку, это не значит, что это — единственная ошибка и еще вы не знаете, где произошла эта ошибка.
Заметьте, что когда OpenGL работает распределенно, как это часто бывает на системах с X11, другие ошибки могут генерироваться, пока у них различные коды. Вызов
glGetErrorтогда просто сбрасывает только один из флагов кодов ошибки вместо всех. Из-за этого и рекомендуют вызывать эту функцию в цикле.
glBindTexture(GL_TEXTURE_2D, tex);
std::cout << glGetError() << std::endl; // вернет 0 (нет ошибки)
glTexImage2D(GL_TEXTURE_3D, 0, GL_RGB, 512, 512, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
std::cout << glGetError() << std::endl; // вернет 1280 (неверное перечисление)
glGenTextures(-5, textures);
std::cout << glGetError() << std::endl; // вернет 1281 (неверное значение
std::cout << glGetError() << std::endl; // вернет 0 (нет ошибки)
Отличительной особенностью glGetError является то, что она позволяет относительно легко определить, где может быть любая ошибка, и проверить правильность использования OpenGL. Скажем, что у вас ничего не отрисовывается, и вы не знаете, в чем причина: неправильно установленный кадровый буфер? Забыл установить текстуру? Вызывая glGetError везде, вы сможете быстро понять, где возникает первая ошибка.
По умолчанию, glGetError сообщает только номер ошибки, который нелегко понять, пока вы не заучиваете номера кодов. Часто имеет смысл написать небольшую функцию, помогающую напечатать строку с ошибкой вместе с местом, откуда вызывается функция.
GLenum glCheckError_(const char *file, int line)
{
GLenum errorCode;
while ((errorCode = glGetError()) != GL_NO_ERROR)
{
std::string error;
switch (errorCode)
{
case GL_INVALID_ENUM: error = "INVALID_ENUM"; break;
case GL_INVALID_VALUE: error = "INVALID_VALUE"; break;
case GL_INVALID_OPERATION: error = "INVALID_OPERATION"; break;
case GL_STACK_OVERFLOW: error = "STACK_OVERFLOW"; break;
case GL_STACK_UNDERFLOW: error = "STACK_UNDERFLOW"; break;
case GL_OUT_OF_MEMORY: error = "OUT_OF_MEMORY"; break;
case GL_INVALID_FRAMEBUFFER_OPERATION: error = "INVALID_FRAMEBUFFER_OPERATION"; break;
}
std::cout << error << " | " << file << " (" << line << ")" << std::endl;
}
return errorCode;
}
#define glCheckError() glCheckError_(__FILE__, __LINE__)
Если вы решите сделать больше вызовов glCheckError, будет полезно знать в каком месте произошла ошибка.
glBindBuffer(GL_VERTEX_ARRAY, vbo);
glCheckError();
Вывод:
Осталась одна важная вещь: в GLEW есть давний баг: glewInit() всегда выставляет флаг GL_INVALID_ENUM. Чтобы это исправить, просто вызывайте glGetError после glewInit чтобы сбросить флаг:
glewInit();
glGetError();
glGetError не сильно помогает, поскольку возвращаемая информация относительно проста, но часто помогает отловить опечатки или отловить место возникновения ошибки. Это простой, но эффективный инструмент для отладки.
Отладочный вывод
Инструмент менее известный, но полезнее, чем glCheckError — расширение OpenGL «debug output» (Отладочный вывод), вошедшее в OpenGL 4.3 Core Profile. С этим расширением OpenGL отошлет сообщение об ошибке пользователю с деталями ошибки. Это расширение не только выдает больше информации, но и позволяет отловить ошибки там, где они возникают, используя отладчик.
Отладочный вывод входит в OpenGL начиная с версии 4.3, что означает, что вы найдете эту функциональность на любой машине, поддерживающей OpenGL 4.3 и выше. Если такая версия недоступна, то можно проверить расширения
ARB_debug_outputиAMD_debug_output. Также есть непроверенная информация о том, что отладочный вывод не поддерживается на OS X (автор оригинала и переводчик не тестировали, прошу сообщать автору оригинала или мне в личные сообщения через механизм исправления ошибок, если найдете подтверждение или опровержение данного факта; UPD: Jeka178RUS проверил этот факт: из коробки отладочный вывод не работает, через расширения он не проверял).
Чтобы начать использовать отладочный вывод, нам надо у OpenGL запросить отладочный контекст во время инициализационного процесса. Этот процесс отличается на разных оконных системах, но здесь мы обсудим только GLFW, но в конце статьи в разделе «Дополнительные материалы» вы можете найти информацию насчет других оконных систем.
Отладочный вывод в GLFW
Запросить отладочный контекст в GLFW на удивление просто: нужно всего лишь дать подсказку GLFW, что мы хотим контекст с поддержкой отладочного вывода. Нам надо сделать это до вызова glfwCreateWindow:
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
Как только мы проинициализировали GLFW, у нас должен появиться отладочный контекст, если мы используем OpenGL 4.3 или выше, иначе нам надо попытать удачу и надеяться на то, что система все еще может создать отладочный контекст. В случае неудачи нам надо запросить отладочный вывод через механизм расширений OpenGL.
Отладочный контекст OpenGL бывает медленнее, чем обычный, так что во время работ над оптимизациями или перед релизом следует убрать или закомментировать эту строчку.
Чтобы проверить результат инициализации отладочного контекста, достаточно выполнить следующий код:
GLint flags; glGetIntegerv(GL_CONTEXT_FLAGS, &flags);
if (flags & GL_CONTEXT_FLAG_DEBUG_BIT)
{
// успешно
}
else
{
// не получилось
}
Как работает отладочный вывод? Мы передаем callback-функцию обработчик сообщений в OpenGL (похоже на callback’и в GLFW) и в этой функции мы можем обрабатывать данные OpenGL как нам угодно, в нашем случае — отправка полезных сообщений об ошибках на консоль. Прототип этой функции:
void APIENTRY glDebugOutput(GLenum source, GLenum type, GLuint id, GLenum severity,
GLsizei length, const GLchar *message, void *userParam);
Заметьте, что на некоторых операционных системах тип последнего параметра может быть const void*.
Учитывая большой набор данных, которыми мы располагаем, мы можем создать полезный инструмент печати ошибок, как показано ниже:
void APIENTRY glDebugOutput(GLenum source,
GLenum type,
GLuint id,
GLenum severity,
GLsizei length,
const GLchar *message,
void *userParam)
{
// ignore non-significant error/warning codes
if(id == 131169 || id == 131185 || id == 131218 || id == 131204) return;
std::cout << "---------------" << std::endl;
std::cout << "Debug message (" << id << "): " << message << std::endl;
switch (source)
{
case GL_DEBUG_SOURCE_API: std::cout << "Source: API"; break;
case GL_DEBUG_SOURCE_WINDOW_SYSTEM: std::cout << "Source: Window System"; break;
case GL_DEBUG_SOURCE_SHADER_COMPILER: std::cout << "Source: Shader Compiler"; break;
case GL_DEBUG_SOURCE_THIRD_PARTY: std::cout << "Source: Third Party"; break;
case GL_DEBUG_SOURCE_APPLICATION: std::cout << "Source: Application"; break;
case GL_DEBUG_SOURCE_OTHER: std::cout << "Source: Other"; break;
} std::cout << std::endl;
switch (type)
{
case GL_DEBUG_TYPE_ERROR: std::cout << "Type: Error"; break;
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: std::cout << "Type: Deprecated Behaviour"; break;
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: std::cout << "Type: Undefined Behaviour"; break;
case GL_DEBUG_TYPE_PORTABILITY: std::cout << "Type: Portability"; break;
case GL_DEBUG_TYPE_PERFORMANCE: std::cout << "Type: Performance"; break;
case GL_DEBUG_TYPE_MARKER: std::cout << "Type: Marker"; break;
case GL_DEBUG_TYPE_PUSH_GROUP: std::cout << "Type: Push Group"; break;
case GL_DEBUG_TYPE_POP_GROUP: std::cout << "Type: Pop Group"; break;
case GL_DEBUG_TYPE_OTHER: std::cout << "Type: Other"; break;
} std::cout << std::endl;
switch (severity)
{
case GL_DEBUG_SEVERITY_HIGH: std::cout << "Severity: high"; break;
case GL_DEBUG_SEVERITY_MEDIUM: std::cout << "Severity: medium"; break;
case GL_DEBUG_SEVERITY_LOW: std::cout << "Severity: low"; break;
case GL_DEBUG_SEVERITY_NOTIFICATION: std::cout << "Severity: notification"; break;
} std::cout << std::endl;
std::cout << std::endl;
}
Когда расширение определяет ошибку OpenGL, оно вызовет эту функцию и мы сможем печатать огромное количество информации об ошибке. Заметьте, мы проигнорировали некоторые ошибки, так как они бесполезны (к примеру, 131185 в драйверах NVidia говорит о том, что буфер успешно создан).
Теперь, когда у нас есть нужный callback, самое время инициализировать отладочный вывод:
if (flags & GL_CONTEXT_FLAG_DEBUG_BIT)
{
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
glDebugMessageCallback(glDebugOutput, nullptr);
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
}
Так мы сообщаем OpenGL, что хотим включить отладочный вывод. Вызов glEnable(GL_DEBUG_SYNCRHONOUS) говорит OpenGL, что мы хотим сообщение об ошибке в тот момент, когда только она произошла.
Фильтрация отладочного вывода
С функцией glDebugMessageControl вы можете выбрать типы ошибок, которые хотите получать. В нашем случае мы получаем все виды ошибок. Если бы мы хотели только ошибки OpenGL API, типа Error и уровня значимости High, мы бы написали следующий код:
glDebugMessageControl(GL_DEBUG_SOURCE_API,
GL_DEBUG_TYPE_ERROR,
GL_DEBUG_SEVERITY_HIGH,
0, nullptr, GL_TRUE);
С такой конфигурацией и отладочным контекстом каждая неверная команда OpenGL будет отправлять много полезной информации:
Находим источник ошибки через стек вызовов
Еще один трюк с отладочным выводом заключается в том, что вы можете относительно просто установить точное место возникновения ошибки в вашем коде. Устанавливая точку останова в функции DebugOutput на нужном типе ошибки (или в начале функции если вы хотите отловить все ошибки) отладчик отловит ошибку и вы сможете переместиться по стеку вызовов, чтобы узнать, где произошла ошибка:
Это требует некоторого ручного вмешательства, но если вы примерно знаете, что ищете, невероятно полезно быстро определить, какой вызов вызывает ошибку.
Свои ошибки
Наряду с чтением ошибок, мы можем их посылать в систему отладочного вывода с помощью glDebugMessageInsert:
glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_ERROR, 0,
GL_DEBUG_SEVERITY_MEDIUM, -1, "error message here");
Это очень полезно, если вы подключаетесь к другому приложению или к коду OpenGL, который использует отладочный контекст. Другие разработчики смогут быстро выяснить любую сообщенную ошибку, которая происходит в вашем пользовательском коде OpenGL.
В общем, отладочный вывод (если доступен) очень полезен для быстрого отлова ошибок и определенно стоит потраченных усилий на настройку, так как экономит значительное время разработки. Вы можете найти копию исходного кода здесь с использованием glGetError и отладочного вывода. Есть ошибки, попробуйте их исправить.
Отладочный вывод шейдера
Когда дело доходит до GLSL, у нас нет доступа к функции типа glGetError или возможности пройтись по коду по шагам в отладчике. Когда вы встречаетесь с черным экраном или совершенно неправильным отображением, бывает очень сложно понять, что происходит, если проблема в шейдере. Да, ошибки компиляции сообщают о синтаксических ошибках, но отлов семантических ошибок — та еще песня.
Один из часто используемых приемов для выяснения того, что не так с шейдером, состоит в том, чтобы отправить все соответствующие переменные в шейдерной программе непосредственно в выходной канал фрагментного шейдера. Выводя шейдерные переменные напрямую в выходной канал с цветом мы можем узнать интересную информацию проверяя картинку на выходе. К примеру, нам надо узнать, правильные ли нормали у модели. Мы можем отправить их (трансформированными или нет) из вершинного в фрагментный шейдер, где мы выведем нормали как-то так:
(прим. пер: почему нет подсветки синтаксиса GLSL?)
#version 330 core
out vec4 FragColor;
in vec3 Normal;
[...]
void main()
{
[...]
FragColor.rgb = Normal;
FragColor.a = 1.0f;
}
Выводя нецветовую переменную в выходной канал с цветом как сейчас, мы можем быстро проверить значение переменной. Если, к примеру, результатом стал черный экран, то ясно, что нормали неправильно переданы в шейдеры, а когда они отображаются, сравнительно легко проверить их на правильность:
Из визуальных результатов мы можем видеть, что нормали верны, так как правая сторона костюма преимущественно красная (что говорит, что нормали примерно показывают в направлении полощительной оси x) и также передняя сторона костюма окрашена в направлении положительной оси z (в синий цвет).
Этот подход можно расширить на любую переменную, которую вы хотите протестировать. Каждый раз, когда вы застряли и предполагаете, что ошибка в шейдерах, попробуйте отрисовывать несколько переменных или промежуточных результатов и выяснить, в какой части алгоритма есть ошибка.
OpenGL GLSL reference compiler
В каждом видеодрайвере свои причуды. К примеру, драйвера NVIDIA немного смягчают требования спецификации, а драйвера AMD лучше соответствую спецификациям (что лучше, как мне кажется). Проблема в том, что шейдеры работающие на одной машине, могут не заработать на другой из-за отличий в драйверах.
За несколько лет опыта вы могли выучить все отличия между различными GPU, но если вы хотите быть уверены в том, что ваши шейдеры будут работать везде, то вы можете сверить ваш код с официальной спецификацией с помощью GLSL reference compiler. Вы можете скачать так называемый GLSL lang validator тут (исходник).
С этой программой вы можете проверить свои шейдеры, передавая их как 1-й аргумент к программе. Помните, что программа определяет тип шейдера по расширению:
.vert: вершинный шейдер.frag: фрагментный шейдер.geom: геометрический шейдер.tesc: тесселяционный контролирующий шейдер.tese: тесселяционный вычислительный шейдер.comp: вычислительный шейдер
Запустить программу легко:
glslangValidator shader.vert
Заметьте, что если нет ошибок, то программа ничего не выведет. На сломанном вершинном шейдере вывод будет похож на:
Программа не покажет различий между компиляторами GLSL от AMD, NVidia или Intel, и даже не может сообщить обо всех багах в шейдере, но он хотя бы проверяет шейдеры на соответствие стандартам.
Вывод буфера кадра
Еще один метод для вашего инструментария — отобразить содержимое кадрового буфера в определенной части экрана. Скорее всего, вы часто используете кадровые буферы и, поскольку вся магия происходит за кадром, бывает трудно определить, что происходит. Вывод содержимого кадрового буфера — полезный прием, чтобы проверить правильность вещей.
Заметьте, что содержимое кадрового буфера, как тут объясняется, работает с текстурами, а не с объектами буферов отрисовки
Используя простой шейдер, который отрисовывает одну текстуру, мы можем написать небольшую функцию, быстро отрисовывающую любую текстуру в правом верхнем углу экрана:
// vertex shader
#version 330 core
layout (location = 0) in vec2 position;
layout (location = 1) in vec2 texCoords;
out vec2 TexCoords;
void main()
{
gl_Position = vec4(position, 0.0f, 1.0f);
TexCoords = texCoords;
}
//fragment shader
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D fboAttachment;
void main()
{
FragColor = texture(fboAttachment, TexCoords);
}
//main.cpp
void DisplayFramebufferTexture(GLuint textureID)
{
if(!notInitialized)
{
// initialize shader and vao w/ NDC vertex coordinates at top-right of the screen
[...]
}
glActiveTexture(GL_TEXTURE0);
glUseProgram(shaderDisplayFBOOutput);
glBindTexture(GL_TEXTURE_2D, textureID);
glBindVertexArray(vaoDebugTexturedRect);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
glUseProgram(0);
}
int main()
{
[...]
while (!glfwWindowShouldClose(window))
{
[...]
DisplayFramebufferTexture(fboAttachment0);
glfwSwapBuffers(window);
}
}
Это даст вам небольшое окошко в углу экрана для отладочного вывода кадрового буфера. Полезно, к примеру, когда пытаешься проверить корректность нормалей:
Вы также можете расширить эту функцию так, чтобы она отрисовывала больше 1 текстуры. Это быстрый путь получить непрерывную отдачу от чего угодно в кадровых буферах.
Внешние программы-отладчики
Когда ничего не помогает, есть еще один прием: воспользоваться сторонними программами. Они встраиваются в драйвера OpenGL и могут перехватывать все вызовы OpenGL, чтобы дать вам очень много интересных данных о вашем приложении. Эти приложения могут профилировать использование функций OpenGL, искать узкие места, наблюдать за кадровыми буферами, текстурами и памятью. Во время работы над (большим) кодом, эти инструменты могут стать бесценными.
Я перечислил несколько популярных инструментов. Попробуйте каждый и выберите тот, который лучше всего вам подходит.
RenderDoc
RenderDoc — хороший (полностью опенсорсный) отдельный отладочный инструмент. Чтобы начать захват, выберите исполняемый файл и рабочую папку (working directory). Ваше приложение работает как обычно, и когда вы хотите понаблюдать за отдельным кадром, вы позволяете RenderDoc снять несколько кадров вашего приложения. Среди захваченных кадров вы можете просмотреть состояние конвейера, все команды OpenGL, хранилище буферов и используемые текстуры.
CodeXL
CodeXL — инструмент отладки GPU, работает как отдельное приложение и плагин к Visual Studio. CodeXL Дает много информации и отлично подходит для профилирования графических приложений. CodeXL также работает на видеокартах от NVidia и Intel, но без поддержки отладки OpenCL.
Я не так много использовал CodeXL, поскольку RenderDoc мне показался проще, но я включил CodeXL в этот список, потому что он выглядит довольно надежным инструментом и в основном разработан одним из крупных производителей графических процессоров.
NVIDIA Nsight
Nsight — популярный инструмент отладки GPU от NUIDIA. Является не только плагином к Visual Studio и Eclipse, но еще и отдельное приложение. Плагин Nsight — очень полезная вещь для графических разработчиков, поскольку собирает много статистик в реальном времени относительно использования GPU и покадрового состояния GPU.
В тот момент, когда вы запускаете свое приложение через Visual Studio или Eclipse с помощью команд отладки или профилирования Nsight, он запустится сам внутри приложения. Хорошая вещь в Nsight: рендер ГИП-системы (GUI, графический интерфейс пользователя) поверх запускаемого приложения, которую можно использовать для собирания информации всех видов о вашем приложении в реальном времени или покадровом анализе.
Nsight — очень полезный инструмент, который, по моему мнению, превосходит вышеперечисленные инструменты, но имеет один серьезный недостаток: работает только на видеокартах от NVIDIA. Если вы работаете на видеокартах от NVIDIA и используете Visual Studio — определенно стоит попробовать Nsight.
Я уверен, что есть еще инструменты для отладки графических приложений (к примеру, VOGL и APItrace), но я считаю, что этот список уже предоставил вам достаточно инструментов для экспериментов. Я не эксперт в вышеупомянутых инструментах, так что если есть ошибки, то пишите мне (переводчику) в личные сообщения и в комментарии к оригинальной статье (если конечно же, там еще осталась эта ошибка).
Дополнительные материалы
- Почему я вижу черный экран? — список возможных случаев появления черного экрана вместо нужной картинки от Reto Koradi.
- Отладочный вывод — обширный список методов настройки отладочного контекста в разных оконных менеджерах от Vallentin Source.
P.S.: У нас есть телеграм-конфа для координации переводов. Если есть серьезное желание помогать с переводом, то милости просим!
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Альтернативное голосование
Проголосовали 30 пользователей.
Воздержались 8 пользователей.
Аннотация: В лекции рассматриваются наиболее часто встречающиеся ошибки при построении изображений с помощью OpenGL и методы их предотвращения и исправления.
Цель лекции: Изучить команду GetError библиотеки OpenGL и познакомится с наиболее часто встречающимися проблемами и вариантами их решения при создании изображений с помощью OpenGL.
При работе с библиотекой OpenGL могут возникать различные ошибки, которые можно условно разделить на несколько групп:
- ошибки формирования изображения;
- некорректное использование команд OpenGL;
- системные ошибки операционной системы.
Далее рассматривается более подробно каждая группа ошибок.
Ошибки формирования изображения
Эта группа ошибок связана с логическими ошибками, которые приводят к некорректному формированию изображения, например:
- объекты находятся за областью сцены и поэтому не отображаются;
- освещенность объектов не соответствует ожидаемой, например, из-за неправильных параметров источника света, свойств материала или нормалей;
- формирование изображения объекта выполняется не правильно из-за ошибки в вычислениях.
В этих случаях выявление ошибки ложится в большей степени на разработчика программы.
Для выявления такого типа ошибок можно использовать некоторые приемы, которые в ряде случаев позволяют упростить поиск причины некорректного формирования изображения. Ниже рассмотрены некоторые типичные ситуации, вызванные такими ошибками, и возможные варианты поиска их причины.
Изображаемый объект не отображается
Причин такой ситуации может быть довольно большое количество.
Прежде всего, необходимо убедиться в том, что объект попадает в область видимости. Если используется перспективная проекция, можно попробовать изменить проекцию на ортогональную и затем увеличить область видимости таким образом, чтобы объект наверняка попал в неё.
Если используется освещение, то его можно попробовать отключить. Необходимо так же проверить, чтобы цвет объектов отличался от цвета фона.
Объект так же может не отображаться из-за его относительно малых размеров по сравнению с размерами отображаемой трехмерной области. Т.е. его размер на проекции может быть менее 1 пиксела. Для выявления этой ситуации можно попробовать увеличить размер объекта.
Сложный объект отображается не корректно
Одной из причин может быть ошибка в расчетах расположения вершин примитивов, из которых состоит объект. В ряде случаев такие ошибки становятся более заметными при уменьшении количества примитивов, из которых формируется объект.
Если объект является объемным, то можно изменить режим отображения многоугольников чтобы их грани отображались в виде отрезков или точек.
Объект некорректно освещается
Одной из наиболее часто встречающихся причин является ошибочное направление или размер нормалей. Так как библиотека не предоставляет готовых средств для отображения нормалей, то это можно сделать самостоятельно при построении примитивов. Если необходимо проверить направление нормалей, то можно задать концам отрезков различные цвета. При этом должен быть включен режим аппроксимации цветов (он включен по умолчанию после инициализации библиотеки) и выключено освещение. В этом случае будет выполняться плавный переход цвета от одного конца отрезка к другому.
Некорректное использование процедур и функций OpenGL
К этой группе ошибок относятся, например, такие как: указание неправильных параметров процедуры или функции библиотеки OpenGL, некорректный порядок их вызова.
Для выявления такого типа ошибок библиотека OpenGL реализует команду GetError:
C#: ErrorCode GetError(); Object Pascal: function glGetError: GLenum
Команда возвращает целочисленный код ошибки. В C# коды ошибок представлены в перечислении ErrorCode, в Object Pascal коды ошибок представлены в виде констант, которые перечислены в модуле Opengl.pas. В таблице 12.1 перечислены наиболее часто встречающиеся на практике коды ошибок команды GetError.
| Описание ошибки | Возможные значения | |
|---|---|---|
| C#, значение перечисления ErrorCode | Object Pascal | |
| Ошибка, возникает при недопустимом значении аргумента типа GLEnum.
В Object Pascal обычно в качестве такого значения выступают константы библиотеки. Примером такого типа ошибки является указание значения параметра для процедуры glBegin, которое не соответствует ни одному из допустимых примитивов. В библиотеке OpenTK (C#) возможность возникновения таких ошибок довольно сильно уменьшена за счет того, что в большинстве случаев допустимые значения параметров представлены в виде перечислений. |
InvalidEnum | GL_INVALID_ENUM |
| Ошибка возникает при некорректном задании значения аргумента функции, тип которого отличается от типа GLEnum. | InvalidValue | GL_INVALID_VALUE |
| Oшибка информирует о вызове команды библиотеки, которая не может быть выполнена в текущем состоянии библиотеки. Например, когда не соблюдается «парность» команд End и Begin. | InvalidOperation | GL_INVALID_OPERATION |
| Эти ошибки возникают при работе с командами, которые используют стеки библиотеки OpenGL. Например, при работе со стеком матриц (команды PushMatrix, PopMatrix), стеком имен (команды PopName, PushName), стеком атрибутов. Ошибка StackOverflow/GL_STACK_OVERFLOW возникает при переполнении стека, ошибка StackUnderflow/GL_STACK_UNDERFLOW – при выходе за нижнюю границу стека. |
StackOverflow, StackUnderflo |
GL_STACK_OVERFLOW, GL_STACK_UNDERFLO |
| Ошибка возникает в ситуации, когда для выполнения операции недостаточно оперативной памяти доступной для библиотеки OpenGL. | OutOfMemory | GL_OUT_OF_MEMORY |
| Ошибка информирует о большом размере одной из специальных таблиц OpenGL, например, таблицы цветов. | TableTooLargeExt | TABLE_TOO_LARGE |
| Данное значение информирует об успешном выполнении операции. | NoError | GL_NO_ERROR |
Если возникает одна из перечисленных ошибок (значение NoError/GL_NO_ERROR не является кодом ошибки), то команда, вызвавшая её, игнорируется, последующие команды продолжают выполняться.
При возникновении ошибки OutOfMemory/GL_OUT_OF_MEMORY результат выполнения команды не определен, так как эта ошибка связана с отсутствием необходимых ресурсов для выполнения команды.
Библиотека сохраняет код возникшей ошибки, пока её значение не будет считано с помощью команды GetError. Если же после вызова команды библиотеки, вызвавшей ошибку, результат её работы не был проанализирован с помощью команды GetError, то библиотека будет сохранять код этой ошибки, пока она не будет считана с помощью команды GetError. Если при выполнении других команд библиотеки также будут возникать ошибки, то информация о них не будет сохранена библиотекой, так как результат первой ошибки не был проанализирован с помощью команды GetError.
Реализованный в библиотеке механизм обнаружения ошибок с помощью команды GetError предполагает, что должен проводиться анализ результата выполнения каждой команды библиотеки OpenGL. На практике такой вариант анализа возможных ошибок реализуется довольно редко, так как приводит к значительному увеличению количества кода программы. Довольно часто используют вариант, когда команда GetError вызывается не после каждой команды, а после каких-то блоков программы. Для своевременного обнаружения ошибок рекомендуется анализировать результат выполнения команд хотя бы при завершении формирования изображения.

В этом уроке мы познакомимся с некоторыми методами и приемами отладки вашей OpenGL-программы. Отладка в OpenGL не так сложна, и изучение некоторых приемов обязательно окупится.
Содержание
Часть 2. Базовое освещение
Часть 3. Загрузка 3D-моделей
Часть 4. Продвинутые возможности OpenGL
Часть 5. Продвинутое освещение
Часть 7. Практика
- Отладка
- Отрисовка текста
glGetError()
Когда вы некорректно используете OpenGL (к примеру, когда настраиваете буфер, забыв его связать (to bind)), OpenGL заметит и создаст один или несколько пользовательских флагов ошибок за кулисами. Мы можем эти ошибки отследить, вызывая функцию glGetError(), которая просто проверяет выставленные флаги ошибок и возвращает значение ошибки, если случились ошибки.
GLenum glGetError();
Эта функция возвращает флаг ошибки или вообще никакую ошибку. Список возвращаемых значений:
Внутри документации к функциям OpenGL вы можете найти коды ошибок, которые генерируются функциями, некорректно используемыми. К примеру, если вы посмотрите на документацию к функции glBindTexture(), то вы сможете найти коды ошибок, генерируемые этой функцией, в разделе «Ошибки» (Errors).
Когда флаг ошибки установлен, никаких других флагов ошибки сгенерировано не будет. Более того, когда glGetError вызывается, функция стирает все флаги ошибок (или только один на распределенной системе, см. ниже). Это значит, что если вы вызываете glGetError один раз после каждого кадра и получаете ошибку, это не значит, что это — единственная ошибка и еще вы не знаете, где произошла эта ошибка.
Заметьте, что когда OpenGL работает распределенно, как это часто бывает на системах с X11, другие ошибки могут генерироваться, пока у них различные коды. Вызов
glGetErrorтогда просто сбрасывает только один из флагов кодов ошибки вместо всех. Из-за этого и рекомендуют вызывать эту функцию в цикле.
glBindTexture(GL_TEXTURE_2D, tex);
std::cout << glGetError() << std::endl; // вернет 0 (нет ошибки)
glTexImage2D(GL_TEXTURE_3D, 0, GL_RGB, 512, 512, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
std::cout << glGetError() << std::endl; // вернет 1280 (неверное перечисление)
glGenTextures(-5, textures);
std::cout << glGetError() << std::endl; // вернет 1281 (неверное значение
std::cout << glGetError() << std::endl; // вернет 0 (нет ошибки)
Отличительной особенностью glGetError является то, что она позволяет относительно легко определить, где может быть любая ошибка, и проверить правильность использования OpenGL. Скажем, что у вас ничего не отрисовывается, и вы не знаете, в чем причина: неправильно установленный кадровый буфер? Забыл установить текстуру? Вызывая glGetError везде, вы сможете быстро понять, где возникает первая ошибка.
По умолчанию, glGetError сообщает только номер ошибки, который нелегко понять, пока вы не заучиваете номера кодов. Часто имеет смысл написать небольшую функцию, помогающую напечатать строку с ошибкой вместе с местом, откуда вызывается функция.
GLenum glCheckError_(const char *file, int line)
{
GLenum errorCode;
while ((errorCode = glGetError()) != GL_NO_ERROR)
{
std::string error;
switch (errorCode)
{
case GL_INVALID_ENUM: error = "INVALID_ENUM"; break;
case GL_INVALID_VALUE: error = "INVALID_VALUE"; break;
case GL_INVALID_OPERATION: error = "INVALID_OPERATION"; break;
case GL_STACK_OVERFLOW: error = "STACK_OVERFLOW"; break;
case GL_STACK_UNDERFLOW: error = "STACK_UNDERFLOW"; break;
case GL_OUT_OF_MEMORY: error = "OUT_OF_MEMORY"; break;
case GL_INVALID_FRAMEBUFFER_OPERATION: error = "INVALID_FRAMEBUFFER_OPERATION"; break;
}
std::cout << error << " | " << file << " (" << line << ")" << std::endl;
}
return errorCode;
}
#define glCheckError() glCheckError_(__FILE__, __LINE__)
Если вы решите сделать больше вызовов glCheckError, будет полезно знать в каком месте произошла ошибка.
glBindBuffer(GL_VERTEX_ARRAY, vbo);
glCheckError();
Вывод:
Осталась одна важная вещь: в GLEW есть давний баг: glewInit() всегда выставляет флаг GL_INVALID_ENUM. Чтобы это исправить, просто вызывайте glGetError после glewInit чтобы сбросить флаг:
glewInit();
glGetError();
glGetError не сильно помогает, поскольку возвращаемая информация относительно проста, но часто помогает отловить опечатки или отловить место возникновения ошибки. Это простой, но эффективный инструмент для отладки.
Отладочный вывод
Инструмент менее известный, но полезнее, чем glCheckError — расширение OpenGL «debug output» (Отладочный вывод), вошедшее в OpenGL 4.3 Core Profile. С этим расширением OpenGL отошлет сообщение об ошибке пользователю с деталями ошибки. Это расширение не только выдает больше информации, но и позволяет отловить ошибки там, где они возникают, используя отладчик.
Отладочный вывод входит в OpenGL начиная с версии 4.3, что означает, что вы найдете эту функциональность на любой машине, поддерживающей OpenGL 4.3 и выше. Если такая версия недоступна, то можно проверить расширения
ARB_debug_outputиAMD_debug_output. Также есть непроверенная информация о том, что отладочный вывод не поддерживается на OS X (автор оригинала и переводчик не тестировали, прошу сообщать автору оригинала или мне в личные сообщения через механизм исправления ошибок, если найдете подтверждение или опровержение данного факта; UPD: Jeka178RUS проверил этот факт: из коробки отладочный вывод не работает, через расширения он не проверял).
Чтобы начать использовать отладочный вывод, нам надо у OpenGL запросить отладочный контекст во время инициализационного процесса. Этот процесс отличается на разных оконных системах, но здесь мы обсудим только GLFW, но в конце статьи в разделе «Дополнительные материалы» вы можете найти информацию насчет других оконных систем.
Отладочный вывод в GLFW
Запросить отладочный контекст в GLFW на удивление просто: нужно всего лишь дать подсказку GLFW, что мы хотим контекст с поддержкой отладочного вывода. Нам надо сделать это до вызова glfwCreateWindow:
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
Как только мы проинициализировали GLFW, у нас должен появиться отладочный контекст, если мы используем OpenGL 4.3 или выше, иначе нам надо попытать удачу и надеяться на то, что система все еще может создать отладочный контекст. В случае неудачи нам надо запросить отладочный вывод через механизм расширений OpenGL.
Отладочный контекст OpenGL бывает медленнее, чем обычный, так что во время работ над оптимизациями или перед релизом следует убрать или закомментировать эту строчку.
Чтобы проверить результат инициализации отладочного контекста, достаточно выполнить следующий код:
GLint flags; glGetIntegerv(GL_CONTEXT_FLAGS, &flags);
if (flags & GL_CONTEXT_FLAG_DEBUG_BIT)
{
// успешно
}
else
{
// не получилось
}
Как работает отладочный вывод? Мы передаем callback-функцию обработчик сообщений в OpenGL (похоже на callback’и в GLFW) и в этой функции мы можем обрабатывать данные OpenGL как нам угодно, в нашем случае — отправка полезных сообщений об ошибках на консоль. Прототип этой функции:
void APIENTRY glDebugOutput(GLenum source, GLenum type, GLuint id, GLenum severity,
GLsizei length, const GLchar *message, void *userParam);
Заметьте, что на некоторых операционных системах тип последнего параметра может быть const void*.
Учитывая большой набор данных, которыми мы располагаем, мы можем создать полезный инструмент печати ошибок, как показано ниже:
void APIENTRY glDebugOutput(GLenum source,
GLenum type,
GLuint id,
GLenum severity,
GLsizei length,
const GLchar *message,
void *userParam)
{
// ignore non-significant error/warning codes
if(id == 131169 || id == 131185 || id == 131218 || id == 131204) return;
std::cout << "---------------" << std::endl;
std::cout << "Debug message (" << id << "): " << message << std::endl;
switch (source)
{
case GL_DEBUG_SOURCE_API: std::cout << "Source: API"; break;
case GL_DEBUG_SOURCE_WINDOW_SYSTEM: std::cout << "Source: Window System"; break;
case GL_DEBUG_SOURCE_SHADER_COMPILER: std::cout << "Source: Shader Compiler"; break;
case GL_DEBUG_SOURCE_THIRD_PARTY: std::cout << "Source: Third Party"; break;
case GL_DEBUG_SOURCE_APPLICATION: std::cout << "Source: Application"; break;
case GL_DEBUG_SOURCE_OTHER: std::cout << "Source: Other"; break;
} std::cout << std::endl;
switch (type)
{
case GL_DEBUG_TYPE_ERROR: std::cout << "Type: Error"; break;
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: std::cout << "Type: Deprecated Behaviour"; break;
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: std::cout << "Type: Undefined Behaviour"; break;
case GL_DEBUG_TYPE_PORTABILITY: std::cout << "Type: Portability"; break;
case GL_DEBUG_TYPE_PERFORMANCE: std::cout << "Type: Performance"; break;
case GL_DEBUG_TYPE_MARKER: std::cout << "Type: Marker"; break;
case GL_DEBUG_TYPE_PUSH_GROUP: std::cout << "Type: Push Group"; break;
case GL_DEBUG_TYPE_POP_GROUP: std::cout << "Type: Pop Group"; break;
case GL_DEBUG_TYPE_OTHER: std::cout << "Type: Other"; break;
} std::cout << std::endl;
switch (severity)
{
case GL_DEBUG_SEVERITY_HIGH: std::cout << "Severity: high"; break;
case GL_DEBUG_SEVERITY_MEDIUM: std::cout << "Severity: medium"; break;
case GL_DEBUG_SEVERITY_LOW: std::cout << "Severity: low"; break;
case GL_DEBUG_SEVERITY_NOTIFICATION: std::cout << "Severity: notification"; break;
} std::cout << std::endl;
std::cout << std::endl;
}
Когда расширение определяет ошибку OpenGL, оно вызовет эту функцию и мы сможем печатать огромное количество информации об ошибке. Заметьте, мы проигнорировали некоторые ошибки, так как они бесполезны (к примеру, 131185 в драйверах NVidia говорит о том, что буфер успешно создан).
Теперь, когда у нас есть нужный callback, самое время инициализировать отладочный вывод:
if (flags & GL_CONTEXT_FLAG_DEBUG_BIT)
{
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
glDebugMessageCallback(glDebugOutput, nullptr);
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
}
Так мы сообщаем OpenGL, что хотим включить отладочный вывод. Вызов glEnable(GL_DEBUG_SYNCRHONOUS) говорит OpenGL, что мы хотим сообщение об ошибке в тот момент, когда только она произошла.
Фильтрация отладочного вывода
С функцией glDebugMessageControl вы можете выбрать типы ошибок, которые хотите получать. В нашем случае мы получаем все виды ошибок. Если бы мы хотели только ошибки OpenGL API, типа Error и уровня значимости High, мы бы написали следующий код:
glDebugMessageControl(GL_DEBUG_SOURCE_API,
GL_DEBUG_TYPE_ERROR,
GL_DEBUG_SEVERITY_HIGH,
0, nullptr, GL_TRUE);
С такой конфигурацией и отладочным контекстом каждая неверная команда OpenGL будет отправлять много полезной информации:
Находим источник ошибки через стек вызовов
Еще один трюк с отладочным выводом заключается в том, что вы можете относительно просто установить точное место возникновения ошибки в вашем коде. Устанавливая точку останова в функции DebugOutput на нужном типе ошибки (или в начале функции если вы хотите отловить все ошибки) отладчик отловит ошибку и вы сможете переместиться по стеку вызовов, чтобы узнать, где произошла ошибка:
Это требует некоторого ручного вмешательства, но если вы примерно знаете, что ищете, невероятно полезно быстро определить, какой вызов вызывает ошибку.
Свои ошибки
Наряду с чтением ошибок, мы можем их посылать в систему отладочного вывода с помощью glDebugMessageInsert:
glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_ERROR, 0,
GL_DEBUG_SEVERITY_MEDIUM, -1, "error message here");
Это очень полезно, если вы подключаетесь к другому приложению или к коду OpenGL, который использует отладочный контекст. Другие разработчики смогут быстро выяснить любую сообщенную ошибку, которая происходит в вашем пользовательском коде OpenGL.
В общем, отладочный вывод (если доступен) очень полезен для быстрого отлова ошибок и определенно стоит потраченных усилий на настройку, так как экономит значительное время разработки. Вы можете найти копию исходного кода здесь с использованием glGetError и отладочного вывода. Есть ошибки, попробуйте их исправить.
Отладочный вывод шейдера
Когда дело доходит до GLSL, у нас нет доступа к функции типа glGetError или возможности пройтись по коду по шагам в отладчике. Когда вы встречаетесь с черным экраном или совершенно неправильным отображением, бывает очень сложно понять, что происходит, если проблема в шейдере. Да, ошибки компиляции сообщают о синтаксических ошибках, но отлов семантических ошибок — та еще песня.
Один из часто используемых приемов для выяснения того, что не так с шейдером, состоит в том, чтобы отправить все соответствующие переменные в шейдерной программе непосредственно в выходной канал фрагментного шейдера. Выводя шейдерные переменные напрямую в выходной канал с цветом мы можем узнать интересную информацию проверяя картинку на выходе. К примеру, нам надо узнать, правильные ли нормали у модели. Мы можем отправить их (трансформированными или нет) из вершинного в фрагментный шейдер, где мы выведем нормали как-то так:
(прим. пер: почему нет подсветки синтаксиса GLSL?)
#version 330 core
out vec4 FragColor;
in vec3 Normal;
[...]
void main()
{
[...]
FragColor.rgb = Normal;
FragColor.a = 1.0f;
}
Выводя нецветовую переменную в выходной канал с цветом как сейчас, мы можем быстро проверить значение переменной. Если, к примеру, результатом стал черный экран, то ясно, что нормали неправильно переданы в шейдеры, а когда они отображаются, сравнительно легко проверить их на правильность:
Из визуальных результатов мы можем видеть, что нормали верны, так как правая сторона костюма преимущественно красная (что говорит, что нормали примерно показывают в направлении полощительной оси x) и также передняя сторона костюма окрашена в направлении положительной оси z (в синий цвет).
Этот подход можно расширить на любую переменную, которую вы хотите протестировать. Каждый раз, когда вы застряли и предполагаете, что ошибка в шейдерах, попробуйте отрисовывать несколько переменных или промежуточных результатов и выяснить, в какой части алгоритма есть ошибка.
OpenGL GLSL reference compiler
В каждом видеодрайвере свои причуды. К примеру, драйвера NVIDIA немного смягчают требования спецификации, а драйвера AMD лучше соответствую спецификациям (что лучше, как мне кажется). Проблема в том, что шейдеры работающие на одной машине, могут не заработать на другой из-за отличий в драйверах.
За несколько лет опыта вы могли выучить все отличия между различными GPU, но если вы хотите быть уверены в том, что ваши шейдеры будут работать везде, то вы можете сверить ваш код с официальной спецификацией с помощью GLSL reference compiler. Вы можете скачать так называемый GLSL lang validator тут (исходник).
С этой программой вы можете проверить свои шейдеры, передавая их как 1-й аргумент к программе. Помните, что программа определяет тип шейдера по расширению:
.vert: вершинный шейдер.frag: фрагментный шейдер.geom: геометрический шейдер.tesc: тесселяционный контролирующий шейдер.tese: тесселяционный вычислительный шейдер.comp: вычислительный шейдер
Запустить программу легко:
glslangValidator shader.vert
Заметьте, что если нет ошибок, то программа ничего не выведет. На сломанном вершинном шейдере вывод будет похож на:
Программа не покажет различий между компиляторами GLSL от AMD, NVidia или Intel, и даже не может сообщить обо всех багах в шейдере, но он хотя бы проверяет шейдеры на соответствие стандартам.
Вывод буфера кадра
Еще один метод для вашего инструментария — отобразить содержимое кадрового буфера в определенной части экрана. Скорее всего, вы часто используете кадровые буферы и, поскольку вся магия происходит за кадром, бывает трудно определить, что происходит. Вывод содержимого кадрового буфера — полезный прием, чтобы проверить правильность вещей.
Заметьте, что содержимое кадрового буфера, как тут объясняется, работает с текстурами, а не с объектами буферов отрисовки
Используя простой шейдер, который отрисовывает одну текстуру, мы можем написать небольшую функцию, быстро отрисовывающую любую текстуру в правом верхнем углу экрана:
// vertex shader
#version 330 core
layout (location = 0) in vec2 position;
layout (location = 1) in vec2 texCoords;
out vec2 TexCoords;
void main()
{
gl_Position = vec4(position, 0.0f, 1.0f);
TexCoords = texCoords;
}
//fragment shader
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D fboAttachment;
void main()
{
FragColor = texture(fboAttachment, TexCoords);
}
//main.cpp
void DisplayFramebufferTexture(GLuint textureID)
{
if(!notInitialized)
{
// initialize shader and vao w/ NDC vertex coordinates at top-right of the screen
[...]
}
glActiveTexture(GL_TEXTURE0);
glUseProgram(shaderDisplayFBOOutput);
glBindTexture(GL_TEXTURE_2D, textureID);
glBindVertexArray(vaoDebugTexturedRect);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
glUseProgram(0);
}
int main()
{
[...]
while (!glfwWindowShouldClose(window))
{
[...]
DisplayFramebufferTexture(fboAttachment0);
glfwSwapBuffers(window);
}
}
Это даст вам небольшое окошко в углу экрана для отладочного вывода кадрового буфера. Полезно, к примеру, когда пытаешься проверить корректность нормалей:
Вы также можете расширить эту функцию так, чтобы она отрисовывала больше 1 текстуры. Это быстрый путь получить непрерывную отдачу от чего угодно в кадровых буферах.
Внешние программы-отладчики
Когда ничего не помогает, есть еще один прием: воспользоваться сторонними программами. Они встраиваются в драйвера OpenGL и могут перехватывать все вызовы OpenGL, чтобы дать вам очень много интересных данных о вашем приложении. Эти приложения могут профилировать использование функций OpenGL, искать узкие места, наблюдать за кадровыми буферами, текстурами и памятью. Во время работы над (большим) кодом, эти инструменты могут стать бесценными.
Я перечислил несколько популярных инструментов. Попробуйте каждый и выберите тот, который лучше всего вам подходит.
RenderDoc
RenderDoc — хороший (полностью опенсорсный) отдельный отладочный инструмент. Чтобы начать захват, выберите исполняемый файл и рабочую папку (working directory). Ваше приложение работает как обычно, и когда вы хотите понаблюдать за отдельным кадром, вы позволяете RenderDoc снять несколько кадров вашего приложения. Среди захваченных кадров вы можете просмотреть состояние конвейера, все команды OpenGL, хранилище буферов и используемые текстуры.
CodeXL
CodeXL — инструмент отладки GPU, работает как отдельное приложение и плагин к Visual Studio. CodeXL Дает много информации и отлично подходит для профилирования графических приложений. CodeXL также работает на видеокартах от NVidia и Intel, но без поддержки отладки OpenCL.
Я не так много использовал CodeXL, поскольку RenderDoc мне показался проще, но я включил CodeXL в этот список, потому что он выглядит довольно надежным инструментом и в основном разработан одним из крупных производителей графических процессоров.
NVIDIA Nsight
Nsight — популярный инструмент отладки GPU от NUIDIA. Является не только плагином к Visual Studio и Eclipse, но еще и отдельное приложение. Плагин Nsight — очень полезная вещь для графических разработчиков, поскольку собирает много статистик в реальном времени относительно использования GPU и покадрового состояния GPU.
В тот момент, когда вы запускаете свое приложение через Visual Studio или Eclipse с помощью команд отладки или профилирования Nsight, он запустится сам внутри приложения. Хорошая вещь в Nsight: рендер ГИП-системы (GUI, графический интерфейс пользователя) поверх запускаемого приложения, которую можно использовать для собирания информации всех видов о вашем приложении в реальном времени или покадровом анализе.
Nsight — очень полезный инструмент, который, по моему мнению, превосходит вышеперечисленные инструменты, но имеет один серьезный недостаток: работает только на видеокартах от NVIDIA. Если вы работаете на видеокартах от NVIDIA и используете Visual Studio — определенно стоит попробовать Nsight.
Я уверен, что есть еще инструменты для отладки графических приложений (к примеру, VOGL и APItrace), но я считаю, что этот список уже предоставил вам достаточно инструментов для экспериментов. Я не эксперт в вышеупомянутых инструментах, так что если есть ошибки, то пишите мне (переводчику) в личные сообщения и в комментарии к оригинальной статье (если конечно же, там еще осталась эта ошибка).
Дополнительные материалы
- Почему я вижу черный экран? — список возможных случаев появления черного экрана вместо нужной картинки от Reto Koradi.
- Отладочный вывод — обширный список методов настройки отладочного контекста в разных оконных менеджерах от Vallentin Source.
P.S.: У нас есть телеграм-конфа для координации переводов. Если есть серьезное желание помогать с переводом, то милости просим!
Автор: developerxyz
Источник
Many of the features of the OpenGL API are very useful and powerful. But, it’s quite possible that OpenGL programs may contain errors. So, it becomes important to learn error handling in OpenGL programs. The OpenGL and GLU libraries have a simple method of recording errors. When an OpenGL program encounters an error in a call to a base library routine or a GLU routine, it records an error code internally, and the routine which caused the error is ignored. Although, OpenGL records only a single error code at any given time. OpenGL uses its own methods to detect errors. Once an error occurs, no other error code will be recorded until the program explicitly queries the OpenGL error state.
Syntax:
GLenum code;
code = glGetError ();
This function call returns the current error code and clears the internal error flag:
- If the returned value is equal to the GLNOERROR OpenGL symbolic constant, everything is fine.
- If there is any other return value, then it indicates that a problem has occurred.
The base OpenGL library provides a definition for a number of symbolic constants which represent different error conditions. The GLU library also defines a number of error codes, but most of them have almost meaningless names such as GLUNURBSERROR1, GLUNURBSERROR2, and so on.
The GLU library contains a function that returns a descriptive string for each of the GLU and GL errors. To use it, first retrieve the current error code and then pass it as a parameter to this function. The return value can be printed out using the C standard library functions like fprintf() function.
Below is the code snippet to implement the above approach:
C
#include <stdio.h>
GLenum code;
const GLubyte* string;
code = glGetError();
string = gluErrorString(code);
fprintf(stderr, "OpenGL error: %sn", string);
Explanation: The value returned by gluErrorString points to a string located inside the GLU library. Since it is not a dynamically allocated string, so it must not be explicitly deallocated by the program. Additionally, it mustn’t be modified by the program (therefore, the const modifier on the declaration of string). It is quite easy to encapsulate these function calls into a general error-reporting function in the program.
The function given below will retrieve the current error code, print the descriptive error string, and return the code to the calling routine:
C
#include <stdio.h>
GLenum errorCheck()
{
GLenum code;
const GLubyte* string;
code = glGetError();
if (code != GL_NO_ERROR) {
string = gluErrorString(code);
fprintf(stderr, "OpenGL error: %sn", string);
}
return code;
}
Explanation: By default, glGetError only prints error numbers, which isn’t easy to understand unless error codes have been memorized already. Thus, it often makes sense to write a small helper function to easily print out the error strings together with where the error check function was called:
GLenum glCheckError_(const char *file, int line)
{
GLenum errorCode;
while ((errorCode = glGetError()) != GL_NO_ERROR)
{
std::string error;
switch (errorCode)
{
case GL_INVALID_ENUM: error = “INVALID_ENUM”; break;
case GL_INVALID_VALUE: error = “INVALID_VALUE”; break;
case GL_INVALID_OPERATION: error = “INVALID_OPERATION”; break;
case GL_STACK_OVERFLOW: error = “STACK_OVERFLOW”; break;
case GL_STACK_UNDERFLOW: error = “STACK_UNDERFLOW”; break;
case GL_OUT_OF_MEMORY: error = “OUT_OF_MEMORY”; break;
case GL_INVALID_FRAMEBUFFER_OPERATION: error = “INVALID_FRAMEBUFFER_OPERATION”; break;
}
std::cout << error << ” | ” << file << ” (” << line << “)” << std::endl;
}
return errorCode;
}
#define glCheckError() glCheckError_(__FILE__, __LINE__)
It’s helpful to more precisely know which glCheckError call returned the error if any of these glCheckError calls are grouped in our database.
glBindBuffer(GL_VERTEX_ARRAY, vbo);
glCheckError();
Output Example:
Last Updated :
18 May, 2021
Like Article
Save Article
В статье глубже описана структура API OpenGL, обработка ошибок, работа с расширениями и получение информации о возможностях драйвера.
Содержание
Структура API OpenGL
API OpenGL описан на языке C без применения C++ ради простоты и платформонезависимости. Он состоит только из функций, констант и примитивных типов, объявленных через typedef, таких как "typedef int GLenum;".
Функции делятся на две группы:
- команды (англ. commands) для изменения состояния драйвера
- запросы (англ. queries) состояния драйвера
Вот несколько примеров:
- функция-команда
void glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha)устанавливает цвет очистки; RGBA компоненты цвета передаются как число с плавающей точкой на отрезке[0..1]. - функция-команда
void glClear()очищает буфер кадра путём заливки пикселей цветом очистки. - функция-запрос
const GLubyte *glGetString(GLenum name)возвращает строковое значение некоторой константы или величины в видеодрайвере, выбор величины зависит от параметраname; при этомconst GLubyte*можно преобразовать вconst char*с помощьюreinterpret_cast. - тип данных
GLclampfозначает “число с плавающей точкой на отрезке[0..1]”; при этом никаких проверок принадлежности диапазону компилятор делать не будет, потому что тип объявлен просто какtypedef float GLclampf.
Функции-команды ничего не возвращают, даже статуса своего выполнения. Это даёт возможность выполнить команду асинхронно, не заставляя приложение ждать, пока видеодрайвер отправит данные на видеокарту и получит от неё ответ.
Обработка ошибок
OpenGL старательно обрабатывает ошибки, такие как “недопустимый аргумент”, “неправильная константа enum”, “несвоевременный вызов команды”. Узнать о наличии общей ошибки в одной из предыдущих функций-команд можно функцией-запросом GLenum glGetError().
- Если функция возвращает
GL_NO_ERROR, ошибок не было - В противном случае код ошибки обозначает категорию ошибки без конкретных указаний
- Функция не только возвращает код ошибки, но и очищает флаг ошибки в драйвере
Условно, код может выглядеть так:
void ValidateGLErrors()
{
GLenum error = glGetError();
if (error != GL_NO_ERROR)
{
std::string message;
// с помощью switch превращаем GLenum в строковое описание
// печатаем строку или делаем ещё что-то в целях отладки
}
}
Функцию можно улучшить, если учесть следующее:
- Распечатать строку ошибки можно в поток ошибок
std::cerr - Любую ошибку можно считать фатальной, вызывая
std::abortдля аварийного завершения программы после вывода текста ошибки - Функцию можно сделать статическим методом класса CUtils
Представим улучшенную версию:
void CUtils::ValidateOpenGLErrors()
{
GLenum error = glGetError();
if (error != GL_NO_ERROR)
{
std::string message;
switch (error)
{
case GL_INVALID_ENUM:
message = "invalid enum passed to GL function (GL_INVALID_ENUM)";
break;
case GL_INVALID_VALUE:
message = "invalid parameter passed to GL function (GL_INVALID_VALUE)";
break;
case GL_INVALID_OPERATION:
message = "cannot execute some of GL functions in current state (GL_INVALID_OPERATION)";
break;
case GL_STACK_OVERFLOW:
message = "matrix stack overflow occured inside GL (GL_STACK_OVERFLOW)";
break;
case GL_STACK_UNDERFLOW:
message = "matrix stack underflow occured inside GL (GL_STACK_UNDERFLOW)";
break;
case GL_OUT_OF_MEMORY:
message = "no enough memory to execute GL function (GL_OUT_OF_MEMORY)";
break;
default:
message = "error in some GL extension (framebuffers, shaders, etc)";
break;
}
std::cerr << "OpenGL error: " << message << std::endl;
std::abort();
}
}
После добавления этого метода можно улучшить основной цикл приложения:
// Очистка буфера кадра, обновление и рисование сцены, вывод буфера кадра.
if (running)
{
m_pImpl->Clear();
const float deltaSeconds = chronometer.GrabDeltaTime();
OnUpdateWindow(deltaSeconds);
OnDrawWindow(m_pImpl->GetWindowSize());
CUtils::ValidateOpenGLErrors();
m_pImpl->SwapBuffers();
}
Расширения OpenGL
В целях максимальной гибкости, все изменения в OpenGL вносятся в виде расширений. Расширение OpenGL — это задокументированная спецификация, которая описывает новые функции и их поведение, изменения в поведении старых функций и новые константы. Каждое расширение имеет имя, например, "GL_ARB_multitexture". При выпуске новой версии OpenGL часть расширений попадает в новую версию и становится частью ядра OpenGL. Таким образом, в версии OpenGL 3.0 и выше вы автоматически получаете ряд возможностей, которые в OpenGL 1.2 были доступны только как расширения.
- В UNIX-системах и на мобильных устройствах доступны достаточно свежие версии OpenGL (обычно 3.0 и выше), где многие важные расширения уже стали частью ядра стандарта.
- В Windows версии старше OpenGL 1.1 напрямую недоступны, но разработчики драйверов дают доступ к ним через механизм расширений. Если видеодрайвер не установлен, будет доступен только OpenGL 1.1, обладающий весьма ограниченными возможностями.
Функция, описанная в расширении, может не существовать в конкретной реализации OpenGL (если она не поддерживает данное расширение). Поэтому программист должен
- либо запросить адрес функции и использовать её, только если адрес ненулевой
- либо проверить наличие поддержки расширения по его имени и потом смело запрашивать адреса описанных в расширении функций
В стандарте OpenGL не описан способ получения адреса, и каждая операционная система или мультимедийная библиотека предоставляет свой способ. В SDL2 есть функция void *SDL_GL_GetProcAddress(const char *proc), которая по имени OpenGL-функции возвращает её адрес или nullptr, если функция недоступна.
Получение информации о версии OpenGL
Один и тот же видеодрайвер может создать разные констексты с разными версиями OpenGL и разными наборами расширений. Поэтому получать версионную информацию следует уже после создания контекста.
Для получения информации мы применим функцию-запрос glGetString с тремя различными параметрами. На эту тему есть статья Get Context Info (opengl.org).
- константа с именем 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 содержит полный список расширений, разделённый пробелами. Список обычно насчитывает свыше ста расширений.
Функция печати информации о контексте
void PrintOpenGLInfo()
{
std::string version = reinterpret_cast<const char *>(glGetString(GL_VERSION));
std::string vendorInfo = reinterpret_cast<const char *>(glGetString(GL_VENDOR));
std::string extensionsInfo = reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS));
std::cerr << "OpenGL version: " << version << std::endl;
std::cerr << "OpenGL vendor: " << vendorInfo << std::endl;
std::cerr << "Full OpenGL extensions list: " << extensionsInfo << std::endl;
}
Следуя “правилу трёх ударов”, можно отрефакторить этот код:
void PrintOpenGLInfo()
{
auto printOpenGLString = [](const char *description, GLenum name) {
std::string info = reinterpret_cast<const char *>(glGetString(name));
std::cerr << description << info << std::endl;
};
printOpenGLString("OpenGL version: ", GL_VERSION);
printOpenGLString("OpenGL vendor: ", GL_VENDOR);
printOpenGLString("Full OpenGL extensions list: ", GL_EXTENSIONS);
}
Библиотека GLEW
- Сайт проекта: http://glew.sourceforge.net/
- В Debian/Ubuntu доступна в пакете
libglew-dev
Запрашивать функции и проверять расширения вручную не всегда удобно. Для решения этой типовой задачи создана библиотека GLEW (сокращение от “openGL Extensions Wrapper”). С помощью макросов и отложенной загрузки адресов функций эта библиотека позволяет использовать расширения так, как будто бы никаких расширений не существует:
- вы просто вызываете функции по имени; если функции нет, произойдёт разыменование нулевого указания
- также вы можете использовать модифицированное имя расширения (с префиксом “GLEW_” вместо “GL_”) как целочисленную переменную со значением 0 или 1; 1 означает, что расширение есть и доступно, 0 означает, что расширения нет или оно недоступно
- если расширение недоступно, вы не должны вызывать функции расширения, чтобы не получить разыменование нулевого указателя
- если при создании контекста OpenGL вы потребовали и получили контекст не ниже определённой версии, то можно даже не проверять расширения, вошедшие в эту версию: они есть.
Подключать заголовок glew.h следует до первого включения gl.h, иначе вы получите ошибку при компиляции.
// Правильно
#include <GL/glew.h>
#include <GL/gl.h>
// Неправильно!
#include <GL/gl.h>
#include <GL/glew.h>
Библиотека GLEW требует явного вызова функции glewInit для своей инициализации. Сделать вызов следует только один раз. Чтобы не накладывать на класс CAbstractWindow лишних ограничений, нужно гарантировать, что при первом конструировании объекта CAbstractWindow функция будет вызвана, а при последующих — уже нет. Также надо установить глобальную переменную-флаг glewExperimental, чтобы GLEW оборачивала функции из версий OpenGL 3.x и 4.x.
Для этой цели можно использовать два подхода
- взять из стандартного заголовка
<mutex>функцию std::call_once - завести в функции статическую переменную типа bool, которая будет устанавливаться в false в инициализаторе (который для статических переменных внутри функции вызывается ровно один раз)
В многопоточной среде было бы правильным использовать call_once, чтобы исключить возможность повторного вызова инициализации во время выполнения “glewInit” в другом потоке. Однако, ни контекст OpenGL, ни GLEW не могут использоваться из нескольких потоков одновременно. Поэтому call_once нам не потребуется, и достаточно статической переменной типа bool:
void CUtils::InitOnceGLEW()
{
static bool didInit = false;
if (!didInit)
{
glewExperimental = GL_TRUE;
GLenum status = glewInit();
if (status != GLEW_OK)
{
std::cerr << "GLEW initialization failed: " << glewGetErrorString(status) << std::endl;
std::abort();
}
}
}
Узнаём о расширениях через GLEW
Читать полный список расширений, полученный через glGetString(GL_EXTENSIONS), не очень удобно. Сканировать его программно слишком трудоёмко в плане вычислений.
Для удобного получения расширений у GLEW есть переменные-флаги, которые устанавливаются при вызове glewInit(). Для проверки наличия расширения надо:
- найти идентификатор расширения в реестре расширений (opengl.org), например,
GL_ARB_vertex_shader - заменить префикс
GL_наGLEW_ - написать проверку переменной-флага с таким именем
Теперь можно улучшить функцию PrintOpenGLInfo:
void PrintOpenGLInfo()
{
auto printOpenGLString = [](const char *description, GLenum name) {
std::string info = reinterpret_cast<const char *>(glGetString(name));
std::cerr << description << info << std::endl;
};
printOpenGLString("OpenGL version: ", GL_VERSION);
printOpenGLString("OpenGL vendor: ", GL_VENDOR);
if (GLEW_ARB_vertex_shader)
{
std::cerr << "Has vertex shaders" << std::endl;
}
else
{
std::cerr << "Has no vertex shaders" << std::endl;
}
if (GLEW_ARB_fragment_shader)
{
std::cerr << "Has fragment shaders" << std::endl;
}
else
{
std::cerr << "Has no fragment shaders" << std::endl;
}
if (GLEW_ARB_vertex_buffer_object)
{
std::cerr << "Has vertex buffers" << std::endl;
}
else
{
std::cerr << "Has vertex busffers" << std::endl;
}
if (GLEW_ARB_framebuffer_object)
{
std::cerr << "Has framebuffers" << std::endl;
}
else
{
std::cerr << "Has framebuffers" << std::endl;
}
}
Рефакторим код:
void PrintOpenGLInfo()
{
auto printOpenGLString = [](const char *description, GLenum name) {
std::string info = reinterpret_cast<const char *>(glGetString(name));
std::cerr << description << info << std::endl;
};
printOpenGLString("OpenGL version: ", GL_VERSION);
printOpenGLString("OpenGL vendor: ", GL_VENDOR);
auto testExtension = [](const char *description, GLboolean supportFlag) {
const char *prefix = supportFlag ? "Has " : "Has no ";
std::cerr << prefix << description << std::endl;
};
testExtension("vertex shaders", GLEW_ARB_vertex_shader);
testExtension("fragment shaders", GLEW_ARB_fragment_shader);
testExtension("vertex buffers", GLEW_ARB_vertex_buffer_object);
testExtension("framebuffers", GLEW_ARB_framebuffer_object);
}
На машине с Ubuntu 14.04 и встроенной видеокартой Intel программа выводит следующее:
OpenGL version: 3.0 Mesa 10.3.2
OpenGL vendor: Intel Open Source Technology Center
Has vertex shaders
Has fragment shaders
Has vertex buffers
Has framebuffers
На машине с Windows 8 и видеокартой Intel вывод отличается:
OpenGL version: 4.4.0 - Build 20.19.15.4377
OpenGL vendor: Intel
Has vertex shaders
Has fragment shaders
Has vertex buffers
Has framebuffers
Создаём работоспособное приложение
Код запроса версии OpenGL разместим в классе CWindow, потому что в дальнейших примерах нам уже не нужно будет печатать что-либо в консоль.
Файл Window.h
#pragma once
#include "AbstractWindow.h"
class CWindow : public CAbstractWindow
{
// CAbstractWindow interface
protected:
void OnWindowEvent(const SDL_Event &event) override;
void OnUpdateWindow(float deltaSeconds) override;
void OnDrawWindow(const glm::ivec2 &size) override;
private:
void PrintOpenGLInfo();
};
листинг Window.cpp
#include "stdafx.h"
#include "Window.h"
#include <mutex>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cctype>
namespace
{
std::once_flag g_didPrintOpenGLInfo;
}
void CWindow::OnWindowEvent(const SDL_Event &event)
{
(void)event;
}
void CWindow::OnUpdateWindow(float deltaSeconds)
{
(void)deltaSeconds;
}
void CWindow::OnDrawWindow(const glm::ivec2 &size)
{
(void)size;
std::call_once(g_didPrintOpenGLInfo, &CWindow::PrintOpenGLInfo, this);
}
void CWindow::PrintOpenGLInfo()
{
auto printOpenGLString = [](const char *description, GLenum name) {
std::string info = reinterpret_cast<const char *>(glGetString(name));
std::cerr << description << info << std::endl;
};
printOpenGLString("OpenGL version: ", GL_VERSION);
printOpenGLString("OpenGL vendor: ", GL_VENDOR);
auto testExtension = [](const char *description, GLboolean supportFlag) {
const char *prefix = supportFlag ? "Has " : "Has no ";
std::cerr << prefix << description << std::endl;
};
testExtension("vertex shaders", GLEW_ARB_vertex_shader);
testExtension("fragment shaders", GLEW_ARB_fragment_shader);
testExtension("vertex buffers", GLEW_ARB_vertex_buffer_object);
testExtension("framebuffers", GLEW_ARB_framebuffer_object);
}



















