
OpenGL in Qt 5.1 - Part 1 and 2
This article is a translation of the article OpenGL in Qt 5.1 - Part 1 and Part 2
This article is the first in a series. She will show how to use OpenGL in Qt 5 . This article will be a short digression into the history of support for OpenGL in Qt. Then we will go on to describe the first part of the new features that were introduced in Qt 5.1 . Subsequent articles will describe more features, as well as some simple examples of how easily Qt can be used to work with OpenGL.
Qt has a long history of drawing support using OpenGL. Most Qt developers have an idea of QGLWidget and possibly some OpenGL-based engines. This allows you to work with raw OpenGL or use the facilities provided by the QPainter API . In addition to this, Qt provides several useful wrappers around various OpenGL objects: QGLShaderProgram , QGLFramebufferObject , QGLBuffer , etc.
When designing Qt 5, these QGL * classes were marked as "done" and the latest QOpenGL * classes became part of QtGui module ( translator's note - used to be QtOpenGL) The reason for these changes is that the new visualization in Qt Quick 2 is based on OpenGL, and is currently a major part of Qt's graphical offerings. In addition, the new QOpenGL * classes can be used as a direct replacement for the old QGL * classes. Now it is recommended to use QOpenGL * classes from the QtGui library .
Qt 5.0 provides (basically) the same subset of OpenGL functionality that Qt 4.8 provided. This was required, inter alia, for the functionality required in Qt Quick 2. In addition to the functionality of Qt 4.8, Qt 5.0 provides tools for easily creating custom OpenGL windows and contexts on any platform. Now you don’t need to bother with the features of various platforms to create a context that can support the OpenGL Core profile . You can simply use QOpenGLContext and save yourself from gray hair!
In Qt 5.1, our adventures begin with demonstrating more and more OpenGL functionality to make using OpenGL and Qt simple, elegant, and hopefully fun! To this end, KDAB has invested heavily in expanding the boundaries of Qt and OpenGL.
. One of the main reasons for these difficulties is the need to assign the address of the entry point dynamically, at run time, and not at build time (when the linker is able to do this). For example, on Microsoft Windows, the address of any function introduced in OpenGL starting with version 1.1 must be assigned at runtime. That is, it needs to be done for almost all the functions used in modern OpenGL!
To solve these problems, Qt provides a couple of useful utilities: QOpenGLContext :: GetProcAddress () and QOpenGLFunctions. The former can be used to manually assign entry points, while the latter is a class whose methods display a common subset of the OpenGL 2 and OpenGL ES 2 functions . These helpers are as good as possible. The problem is that QOpenGLFunctions is limited in the methods provided (subsets of OpenGL 2 and OpenGL ES 2). A manual assignment of entry points is a very tedious job, fraught with errors. Alternatively, you can use functions from third-party libraries that solve these problems, such as Glew or GLee . True, these solutions also cause problems for obtaining the required functionality and convenient work with Qt (for example, taking into account the order of the headers).
UseQOpenGLContext :: versionFunctions () ! This is a modest little function - your guide to the “utopia” of addresses (entry points) of OpenGL functions :) This function can be used to get a pointer to an object (with methods for each function), in the required version and OpenGL profile. Let's take a look at a simple example. Let's say we have a subclass of QWindow that renders. We want to create an OpenGL 4.3 Core profile and use all the features provided. It is very easy to do:
From now on, we can simply use the member functions of the QOpenGLFunctions_4_3_Core object . For instance:
As we can see, it’s easy enough to have all the OpenGL functionality in your hands for any platform that supports it. In addition, QOpenGLContext , QOpenGLFunctions_4_3_Core, and similar classes minimize the use of functions by distributing backends containing valid function pointers. Also, this approach automatically takes care of platform-specific function addresses (for example, when using multiple threads and contexts or multiple GPUs). The code for these classes is automatically generated using an auxiliary utility, so it’s very easy to update when a new version of OpenGL is released.
OpenGL has a popular extension engine that allows vendors to introduce new or experimental functionality and APIs to check if they are useful and well thought out. Unfortunately, if the extension introduces new functions, then they also need to specify an address as well as for other OpenGL functions (as indicated above).
To use OpenGL extensions, you need to go through two stages. Qt helps with both steps:
As with the core OpenGL features, the code for extensions is generated and therefore easy to update in the future.
Qt has a QOpenGLBuffer (before that there was a QGLBuffer ) to help manage various types of OpenGL buffer objects, such as per-vertex attribute data and element index buffers . OpenGL also has a special container, the type of which is called Objects Vertex Array (VAOs) , which helps with vertex buffer object (VBO) sets .
KDAB has added code for Qt 5.1 that encapsulates multiple VAOs with the QOpenGLVertexArrayObject class. Binding an instance of this class tells OpenGL to “remember” any state of the vertex specification that you would like to set later. We can later restore the required state specifications very quickly by simply reconnecting the VAO itself. This allows us to switch very quickly between vertex states for the “objects” that we want to render in our rendering function:
VAOs have been introduced since OpenGL 3, but they are required for OpenGL versions older than 3.1 with Core Profile. In addition, VAOs are available as GL_ARB_vertex_array_object or GL_OES_vertex_array_object extensions in OpenGL 2 and OpenGL ES 2, respectively. The QOpenGLVertexArrayObject class will use the main functionality (if possible) or refer to the extension (if any) if necessary.
Using VAO can greatly simplify the visualization code and increase productivity, as the OpenGL driver will do fewer validation checks (than if it would do more buffer operations).
Part 3,4,5 coming soon ...PS: A big request, all stylistic and grammatical errors, as well as inaccuracies in the translation, should be reported through the LAN. I will rule everything as they become available.
Part 1
This article is the first in a series. She will show how to use OpenGL in Qt 5 . This article will be a short digression into the history of support for OpenGL in Qt. Then we will go on to describe the first part of the new features that were introduced in Qt 5.1 . Subsequent articles will describe more features, as well as some simple examples of how easily Qt can be used to work with OpenGL.
(Very) A Brief History of Qt and OpenGL
Qt has a long history of drawing support using OpenGL. Most Qt developers have an idea of QGLWidget and possibly some OpenGL-based engines. This allows you to work with raw OpenGL or use the facilities provided by the QPainter API . In addition to this, Qt provides several useful wrappers around various OpenGL objects: QGLShaderProgram , QGLFramebufferObject , QGLBuffer , etc.
When designing Qt 5, these QGL * classes were marked as "done" and the latest QOpenGL * classes became part of QtGui module ( translator's note - used to be QtOpenGL) The reason for these changes is that the new visualization in Qt Quick 2 is based on OpenGL, and is currently a major part of Qt's graphical offerings. In addition, the new QOpenGL * classes can be used as a direct replacement for the old QGL * classes. Now it is recommended to use QOpenGL * classes from the QtGui library .
Qt 5.0 provides (basically) the same subset of OpenGL functionality that Qt 4.8 provided. This was required, inter alia, for the functionality required in Qt Quick 2. In addition to the functionality of Qt 4.8, Qt 5.0 provides tools for easily creating custom OpenGL windows and contexts on any platform. Now you don’t need to bother with the features of various platforms to create a context that can support the OpenGL Core profile . You can simply use QOpenGLContext and save yourself from gray hair!
In Qt 5.1, our adventures begin with demonstrating more and more OpenGL functionality to make using OpenGL and Qt simple, elegant, and hopefully fun! To this end, KDAB has invested heavily in expanding the boundaries of Qt and OpenGL.
Functions, functions everywhere!
Frankly speaking, working with OpenGL on some platforms can be difficult. One of the main reasons for these difficulties is the need to assign the address of the entry point dynamically, at run time, and not at build time (when the linker is able to do this). For example, on Microsoft Windows, the address of any function introduced in OpenGL starting with version 1.1 must be assigned at runtime. That is, it needs to be done for almost all the functions used in modern OpenGL!
To solve these problems, Qt provides a couple of useful utilities: QOpenGLContext :: GetProcAddress () and QOpenGLFunctions. The former can be used to manually assign entry points, while the latter is a class whose methods display a common subset of the OpenGL 2 and OpenGL ES 2 functions . These helpers are as good as possible. The problem is that QOpenGLFunctions is limited in the methods provided (subsets of OpenGL 2 and OpenGL ES 2). A manual assignment of entry points is a very tedious job, fraught with errors. Alternatively, you can use functions from third-party libraries that solve these problems, such as Glew or GLee . True, these solutions also cause problems for obtaining the required functionality and convenient work with Qt (for example, taking into account the order of the headers).
UseQOpenGLContext :: versionFunctions () ! This is a modest little function - your guide to the “utopia” of addresses (entry points) of OpenGL functions :) This function can be used to get a pointer to an object (with methods for each function), in the required version and OpenGL profile. Let's take a look at a simple example. Let's say we have a subclass of QWindow that renders. We want to create an OpenGL 4.3 Core profile and use all the features provided. It is very easy to do:
Window::Window(QScreen * screen) : QWindow(screen) {
// Скажем Qt что мы используем OpenGL для этого окна
setSurfaceType(OpenGLSurface);
// Специфицируем формат и создаем платфоро-зависимый сюрфейс
QSurfaceFormat format;
format.setDepthBufferSize(24);
format.setMajorVersion(4);
format.setMinorVersion(3);
format.setSamples(4);
format.setProfile(QSurfaceFormat::CoreProfile);
setFormat(format);
create();
// Создаем OpenGL контекст
m_context = new QOpenGLContext;
m_context->setFormat(format);
m_context->create();
// Сделаем контекст текущим для этого окна
m_context->makeCurrent(this);
// Получить объект функции и назначить все точки входа
// m_funcs объявлен как: QOpenGLFunctions_4_3_Core * m_funcs
m_funcs = m_context->versionFunctions();
if (!m_funcs) {
qWarning("Could not obtain OpenGL versions object");
exit(1);
}
m_funcs->initializeOpenGLFunctions();
}
From now on, we can simply use the member functions of the QOpenGLFunctions_4_3_Core object . For instance:
// Установить Vertex Attrib Divisor
// Который используется с instanced rendering
// (введен в OpenGL 3.3)
m_funcs->glVertexAttribDivisor(pointLocation, 1);
// Процесс отправки через compute shader
// (введенный в OpenGL 4.3)
m_funcs->glDispatchCompute(512 / 16, 512 / 16, 1);
What is this code about?
As we can see, it’s easy enough to have all the OpenGL functionality in your hands for any platform that supports it. In addition, QOpenGLContext , QOpenGLFunctions_4_3_Core, and similar classes minimize the use of functions by distributing backends containing valid function pointers. Also, this approach automatically takes care of platform-specific function addresses (for example, when using multiple threads and contexts or multiple GPUs). The code for these classes is automatically generated using an auxiliary utility, so it’s very easy to update when a new version of OpenGL is released.
OpenGL Extensions
OpenGL has a popular extension engine that allows vendors to introduce new or experimental functionality and APIs to check if they are useful and well thought out. Unfortunately, if the extension introduces new functions, then they also need to specify an address as well as for other OpenGL functions (as indicated above).
To use OpenGL extensions, you need to go through two stages. Qt helps with both steps:
Stage 1:
Verify that the current implementation supports the required extension. If the extension introduces a new API, specify entry points. To verify that the extension is supported, you need to use the QOpenGLContext :: hasExtension () method . In addition, to get the full list of supported extensions, you can use OpenGLContext :: extensions () :// Очередь расширений
QList extensions = m_context->extensions().toList();
std::sort(extensions);
qDebug() << "Supported extensions (" << extensions.count() <<")";
foreach (const QByteArray &extension, extensions) {
qDebug() << " " << extension;
}
Stage 2:
In the second stage, we would need to use our old friend - the QOpenGLContext :: GetProcAddress () method . In Qt 5.1, the QtOpenGLExtensions module is responsible for this . This module is a static library and contains a class for each OpenGL extension (which introduces a new API) from the Khronos registry . To use the OpenGL extension, use code similar to the following: // Проверка поддержки расширения
if (!m_context->hasExtension(QByteArrayLiteral("GL_ARB_instanced_arrays")) {
qFatal("GL_ARB_instanced_arrays is not supported");
}
// Создаем экземпляр вспомогательного класса и разрешаем использование требуемой функции
QOpenGLExtension_ARB_instanced_arrays * m_instanceFuncs = new QOpenGLExtension_ARB_instanced_arrays();
m_instanceFuncs->initializeOpenGLFunctions();
// Вызов функции расширения
m_instanceFuncs->glVertexAttribDivisorARB(pointLocation, 1);
As with the core OpenGL features, the code for extensions is generated and therefore easy to update in the future.
Part 2
Vertex Array Objects
Qt has a QOpenGLBuffer (before that there was a QGLBuffer ) to help manage various types of OpenGL buffer objects, such as per-vertex attribute data and element index buffers . OpenGL also has a special container, the type of which is called Objects Vertex Array (VAOs) , which helps with vertex buffer object (VBO) sets .
KDAB has added code for Qt 5.1 that encapsulates multiple VAOs with the QOpenGLVertexArrayObject class. Binding an instance of this class tells OpenGL to “remember” any state of the vertex specification that you would like to set later. We can later restore the required state specifications very quickly by simply reconnecting the VAO itself. This allows us to switch very quickly between vertex states for the “objects” that we want to render in our rendering function:
Translator's Note
In my opinion, instead of “binding,” it would be more appropriate to use the “Russified” verb “bind”, which is probably more familiar to developers.
void Scene::initialize() {
// Предположим что мы имеем текущий QOpenGLContext и
// m_shaderProgram экземпляр QOpenGLShaderProgram
// Создаем VAO для рендеринга первого объекта
m_vao1 = new QOpenGLVertexArrayObject( this );
m_vao1->create();
m_vao1->bind();
// Установка нескольких VBO и IBO (используем QOpenGLBuffer для хранения данных,
// указание формата, указание применения и т.д.). Это будет "запомнено"
// с текущим связанным VAO
m_positionBuffer.create();
m_positionBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw);
m_positionBuffer.bind();
m_positionBuffer.allocate(positionData,
vertexCount * 3 * sizeof(float));
m_shaderProgram.enableAttributeArray("vertexPosition");
m_shaderProgram.setAttributeBuffer ("vertexPosition", GL_FLOAT, 0, 3);
m_colorBuffer.create();
m_colorBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
m_colorBuffer.bind();
m_colorBuffer.allocate(colorData,
vertexCount * 3 * sizeof(float));
m_shaderProgram.enableAttributeArray("vertexColor");
m_shaderProgram.setAttributeBuffer ("vertexColor", GL_FLOAT, 0, 3);
// Повторить для буферов нормалей, текстурных координат,
// касательных, ...
...
// Создаем VAO для отображения второго объекта
m_vao2 = new QOpenGLVertexArrayObject(this);
m_vao2->create();
m_vao2->bind();
// Установка нескольких VBO и IBO для следующего объекта
...
// "Промыть и повторить" для других объектов (см. примечание ниже)
m_skyBoxVAO = new QOpenGLVertexArrayObject(this);
...
}
void Scene::render() {
// Очищаем буферы
m_funcs->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Связываем шейдер для первого набор объектов
m_phongShaderProgram->bind();
...
// Переключаемся на данные о вершинах первого объекта и рисуем его
m_vao1->bind();
m_funcs->glDrawElements(...);
// Переключаемся на данные о вершинах второго объекта и рисуем его
m_vao2->bind();
m_funcs->glDrawElements(...);
// Мб поменять шейдер и/или текстуру и т.д.
// И отрисовать остальные объекты
m_skyboxShaderProgram->bind();
...
m_skyboxVAO->bind();
m_funcs->glDrawElements(...);
...
}
Translator's Note
“Rinse and repeat” is a sarcastic metaphor for repeating instructions. It has roots from the phrase “wash, rinse, repeat” , which is indicated on the packaging of most brands of shampoos.
VAOs have been introduced since OpenGL 3, but they are required for OpenGL versions older than 3.1 with Core Profile. In addition, VAOs are available as GL_ARB_vertex_array_object or GL_OES_vertex_array_object extensions in OpenGL 2 and OpenGL ES 2, respectively. The QOpenGLVertexArrayObject class will use the main functionality (if possible) or refer to the extension (if any) if necessary.
Using VAO can greatly simplify the visualization code and increase productivity, as the OpenGL driver will do fewer validation checks (than if it would do more buffer operations).
Part 3,4,5 coming soon ...PS: A big request, all stylistic and grammatical errors, as well as inaccuracies in the translation, should be reported through the LAN. I will rule everything as they become available.