OpenGL Window Example¶
This example shows how to create a minimal
QWindow
based application for the purpose of using OpenGL.Note
This is a low level example of how to use
QWindow
with OpenGL. In practice you should consider using the higher levelQOpenGLWindow
class.
OpenGLWindow Super Class¶
Our OpenGLWindow class acts as an API which is then subclassed to do the actual rendering. It has functions to make a request for render() to be called, either immediately with renderNow() or as soon as the event loop has finished processing the current batch of events with renderLater(). The OpenGLWindow subclass can either reimplement render() for OpenGL based rendering, or render(
QPainter
*) for rendering with aQPainter
. Use OpenGLWindow::setAnimating(true) for render() to be called at the vertical refresh rate, assuming vertical sync is enabled in the underlying OpenGL drivers.In the class that does the OpenGL rendering you will typically want to inherit from
QOpenGLFunctions
, as our OpenGLWindow does, in order to get platform independent access to OpenGL ES 2.0 functions. By inheriting fromQOpenGLFunctions
the OpenGL functions it contains will get precedence, and you will not have to worry about resolving those functions if you want your application to work with OpenGL as well as OpenGL ES 2.0.class OpenGLWindow : public QWindow, protected QOpenGLFunctions { Q_OBJECT public: explicit OpenGLWindow(QWindow *parent = nullptr); ~OpenGLWindow(); virtual void render(QPainter *painter); virtual void render(); virtual void initialize(); void setAnimating(bool animating); public slots: void renderLater(); void renderNow(); protected: bool event(QEvent *event) override; void exposeEvent(QExposeEvent *event) override; private: bool m_animating = false; QOpenGLContext *m_context = nullptr; QOpenGLPaintDevice *m_device = nullptr; };The window’s surface type must be set to
OpenGLSurface
to indicate that the window is to be used for OpenGL rendering and not for rendering raster content withQPainter
using aQBackingStore
.OpenGLWindow::OpenGLWindow(QWindow *parent) : QWindow(parent) { setSurfaceType(QWindow::OpenGLSurface); }Any OpenGL initialization needed can be done by overriding the initialize() function, which is called once before the first call to render(), with a valid current
QOpenGLContext
. As can be seen in the following code snippet, the default render(QPainter
*) and initialize() implementations are empty, whereas the default render() implementation initializes aQOpenGLPaintDevice
and then calls into render(QPainter
*).void OpenGLWindow::render(QPainter *painter) { Q_UNUSED(painter); } void OpenGLWindow::initialize() { } void OpenGLWindow::render() { if (!m_device) m_device = new QOpenGLPaintDevice; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); m_device->setSize(size() * devicePixelRatio()); m_device->setDevicePixelRatio(devicePixelRatio()); QPainter painter(m_device); render(&painter); }The renderLater() function simply calls
requestUpdate()
to schedule an update for when the system is ready to repaint.We also call renderNow() when we get an expose event. The exposeEvent() is the notification to the window that its exposure, meaning visibility, on the screen has changed. When the expose event is received you can query
isExposed()
to find out whether or not the window is currently exposed. Do not render to or callswapBuffers()
on a window before it has received its first expose event, as before then its final size might be unknown, and in addition what is rendered might not even end up on the screen.void OpenGLWindow::renderLater() { requestUpdate(); } bool OpenGLWindow::event(QEvent *event) { switch (event->type()) { case QEvent::UpdateRequest: renderNow(); return true; default: return QWindow::event(event); } } void OpenGLWindow::exposeEvent(QExposeEvent *event) { Q_UNUSED(event); if (isExposed()) renderNow(); }In renderNow() we return if we are not currently exposed, in which case rendering is delayed until we actually get an expose event. If we have not yet done so, we create the
QOpenGLContext
with the sameQSurfaceFormat
as was set on the OpenGLWindow, and call initialize() for the sake of the sub class, and initializeOpenGLFunctions() in order for theQOpenGLFunctions
super class to be associated with the correctQOpenGLContext
. In any case we make the context current by callingmakeCurrent()
, call render() to do the actual rendering, and finally we schedule for the rendered contents to be made visible by callingswapBuffers()
with the OpenGLWindow as parameter.Once the rendering of a frame using an OpenGL context is initiated by calling
makeCurrent()
, giving the surface on which to render as a parameter, OpenGL commands can be issued. The commands can be issued either directly by including <qopengl.h>, which also includes the system’s OpenGL headers, or as by usingQOpenGLFunctions
, which can either be inherited from for convenience, or accessed usingfunctions()
.QOpenGLFunctions
gives access to all the OpenGL ES 2.0 level OpenGL calls that are not already standard in both OpenGL ES 2.0 and desktop OpenGL. For more information about the OpenGL and OpenGL ES APIs, refer to the official OpenGL Registry and Khronos OpenGL ES API Registry .If animation has been enabled with OpenGLWindow::setAnimating(true), we call renderLater() to schedule another update request.
void OpenGLWindow::renderNow() { if (!isExposed()) return; bool needsInitialize = false; if (!m_context) { m_context = new QOpenGLContext(this); m_context->setFormat(requestedFormat()); m_context->create(); needsInitialize = true; } m_context->makeCurrent(this); if (needsInitialize) { initializeOpenGLFunctions(); initialize(); } render(); m_context->swapBuffers(this); if (m_animating) renderLater(); }Enabling animation also schedules an update request as shown in the following code snippet.
void OpenGLWindow::setAnimating(bool animating) { m_animating = animating; if (animating) renderLater(); }
Example OpenGL Rendering Sub Class¶
Here we sub class OpenGLWindow to show how to do OpenGL to render a rotating triangle. By indirectly sub classing
QOpenGLFunctions
we gain access to all OpenGL ES 2.0 level functionality.class TriangleWindow : public OpenGLWindow { public: using OpenGLWindow::OpenGLWindow; void initialize() override; void render() override; private: GLint m_posAttr = 0; GLint m_colAttr = 0; GLint m_matrixUniform = 0; QOpenGLShaderProgram *m_program = nullptr; int m_frame = 0; };In our main function we initialize
QGuiApplication
and instantiate our TriangleOpenGLWindow. We give it aQSurfaceFormat
specifying that we want four samples of multisample antialiasing, as well as a default geometry. Since we want to have animation we call the above mentioned setAnimating() function with an argument of true.int main(int argc, char **argv) { QGuiApplication app(argc, argv); QSurfaceFormat format; format.setSamples(16); TriangleWindow window; window.setFormat(format); window.resize(640, 480); window.show(); window.setAnimating(true); return app.exec(); }The following code snippet shows the OpenGL shader program used in this example. The vertex and fragment shaders are relatively simple, doing vertex transformation and interpolated vertex coloring.
static const char *vertexShaderSource = "attribute highp vec4 posAttr;\n" "attribute lowp vec4 colAttr;\n" "varying lowp vec4 col;\n" "uniform highp mat4 matrix;\n" "void main() {\n" " col = colAttr;\n" " gl_Position = matrix * posAttr;\n" "}\n"; static const char *fragmentShaderSource = "varying lowp vec4 col;\n" "void main() {\n" " gl_FragColor = col;\n" "}\n";Here is the code that loads the shaders and initializes the shader program By using
QOpenGLShaderProgram
instead of raw OpenGL we get the convenience that strips out the highp, mediump, and lowp qualifiers on desktop OpenGL, where they are not part of the standard. We store the attribute and uniform locations in member variables to avoid having to do the location lookup each frame.void TriangleWindow::initialize() { m_program = new QOpenGLShaderProgram(this); m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource); m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource); m_program->link(); m_posAttr = m_program->attributeLocation("posAttr"); Q_ASSERT(m_posAttr != -1); m_colAttr = m_program->attributeLocation("colAttr"); Q_ASSERT(m_colAttr != -1); m_matrixUniform = m_program->uniformLocation("matrix"); Q_ASSERT(m_matrixUniform != -1); }Finally, here is our render() function, where we use OpenGL to set up the viewport, clear the background, and render a rotating triangle.
void TriangleWindow::render() { const qreal retinaScale = devicePixelRatio(); glViewport(0, 0, width() * retinaScale, height() * retinaScale); glClear(GL_COLOR_BUFFER_BIT); m_program->bind(); QMatrix4x4 matrix; matrix.perspective(60.0f, 4.0f / 3.0f, 0.1f, 100.0f); matrix.translate(0, 0, -2); matrix.rotate(100.0f * m_frame / screen()->refreshRate(), 0, 1, 0); m_program->setUniformValue(m_matrixUniform, matrix); static const GLfloat vertices[] = { 0.0f, 0.707f, -0.5f, -0.5f, 0.5f, -0.5f }; static const GLfloat colors[] = { 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f }; glVertexAttribPointer(m_posAttr, 2, GL_FLOAT, GL_FALSE, 0, vertices); glVertexAttribPointer(m_colAttr, 3, GL_FLOAT, GL_FALSE, 0, colors); glEnableVertexAttribArray(m_posAttr); glEnableVertexAttribArray(m_colAttr); glDrawArrays(GL_TRIANGLES, 0, 3); glDisableVertexAttribArray(m_colAttr); glDisableVertexAttribArray(m_posAttr); m_program->release(); ++m_frame; }
© 2022 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.