diff --git a/CMakeLists.txt b/CMakeLists.txt
index 811d85c4663f49e7dcfb75e27e3be5f82e5e160d..7fce33b39c73d1728e4a97f72ebfd0584c331199 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,4 +3,5 @@ add_subdirectory(ordering-system)
 add_subdirectory(gallery_controls2)
 add_subdirectory(rasterwindow)
 add_subdirectory(principledmaterial)
+add_subdirectory(hellogl)
 # (slate is built separately)
\ No newline at end of file
diff --git a/deploy.sh b/deploy.sh
index cc56c84351228b1d280a54d3ea28efcbc90f2acd..a02531a107259f65d2e14c5d8a0571451a4b6e83 100755
--- a/deploy.sh
+++ b/deploy.sh
@@ -33,6 +33,7 @@ deploy "mandelbrot"
 deploy "multicanvas"
 deploy "rasterwindow"
 deploy "principledmaterial"
+deploy "hellogl"
 
 # slate
 rm -rf slate
diff --git a/hellogl/CMakeLists.txt b/hellogl/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..7a9829fe85b03dc4555e34a46e4ed67eb3f7cea8
--- /dev/null
+++ b/hellogl/CMakeLists.txt
@@ -0,0 +1,51 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(hellogl LANGUAGES CXX)
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui OpenGL)
+
+qt_standard_project_setup()
+
+qt_add_executable(hellogl
+    logo.cpp logo.h
+    glwindow.cpp glwindow.h
+    main.cpp
+)
+
+set_target_properties(hellogl PROPERTIES
+    WIN32_EXECUTABLE TRUE
+    MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(hellogl PRIVATE
+    Qt6::Core
+    Qt6::Gui
+    Qt6::OpenGL
+)
+
+# Resources:
+set(hellogl_resource_files
+    "qtlogo.png"
+)
+
+qt_add_resources(hellogl "hellogl"
+    PREFIX
+        "/"
+    FILES
+        ${hellogl_resource_files}
+)
+
+install(TARGETS hellogl
+    BUNDLE  DESTINATION .
+    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+)
+
+qt_generate_deploy_app_script(
+    TARGET hellogl
+    OUTPUT_SCRIPT deploy_script
+    NO_UNSUPPORTED_PLATFORM_ERROR
+)
+install(SCRIPT ${deploy_script})
diff --git a/hellogl/glwindow.cpp b/hellogl/glwindow.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8f19bd38993ac690e83077904e5fe378ee3e99bd
--- /dev/null
+++ b/hellogl/glwindow.cpp
@@ -0,0 +1,229 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "glwindow.h"
+#include <QImage>
+#include <QOpenGLTexture>
+#include <QOpenGLShaderProgram>
+#include <QOpenGLBuffer>
+#include <QOpenGLContext>
+#include <QOpenGLVertexArrayObject>
+#include <QOpenGLExtraFunctions>
+#include <QPropertyAnimation>
+#include <QSequentialAnimationGroup>
+#include <QTimer>
+#include <QPainter>
+
+GLWindow::GLWindow()
+{
+    m_world.setToIdentity();
+    m_world.translate(0, 0, -1);
+    m_world.rotate(180, 1, 0, 0);
+
+    QSequentialAnimationGroup *animGroup = new QSequentialAnimationGroup(this);
+    animGroup->setLoopCount(-1);
+    QPropertyAnimation *zAnim0 = new QPropertyAnimation(this, QByteArrayLiteral("z"));
+    zAnim0->setStartValue(1.5f);
+    zAnim0->setEndValue(10.0f);
+    zAnim0->setDuration(2000);
+    animGroup->addAnimation(zAnim0);
+    QPropertyAnimation *zAnim1 = new QPropertyAnimation(this, QByteArrayLiteral("z"));
+    zAnim1->setStartValue(10.0f);
+    zAnim1->setEndValue(50.0f);
+    zAnim1->setDuration(4000);
+    zAnim1->setEasingCurve(QEasingCurve::OutElastic);
+    animGroup->addAnimation(zAnim1);
+    QPropertyAnimation *zAnim2 = new QPropertyAnimation(this, QByteArrayLiteral("z"));
+    zAnim2->setStartValue(50.0f);
+    zAnim2->setEndValue(1.5f);
+    zAnim2->setDuration(2000);
+    animGroup->addAnimation(zAnim2);
+    animGroup->start();
+
+    QPropertyAnimation* rAnim = new QPropertyAnimation(this, QByteArrayLiteral("r"));
+    rAnim->setStartValue(0.0f);
+    rAnim->setEndValue(360.0f);
+    rAnim->setDuration(2000);
+    rAnim->setLoopCount(-1);
+    rAnim->start();
+
+    QTimer::singleShot(4000, this, &GLWindow::startSecondStage);
+}
+
+GLWindow::~GLWindow()
+{
+    makeCurrent();
+    delete m_texture;
+    delete m_program;
+    delete m_vbo;
+    delete m_vao;
+}
+
+void GLWindow::startSecondStage()
+{
+    QPropertyAnimation* r2Anim = new QPropertyAnimation(this, QByteArrayLiteral("r2"));
+    r2Anim->setStartValue(0.0f);
+    r2Anim->setEndValue(360.0f);
+    r2Anim->setDuration(20000);
+    r2Anim->setLoopCount(-1);
+    r2Anim->start();
+}
+
+void GLWindow::setZ(float v)
+{
+    m_eye.setZ(v);
+    m_uniformsDirty = true;
+    update();
+}
+
+void GLWindow::setR(float v)
+{
+    m_r = v;
+    m_uniformsDirty = true;
+    update();
+}
+
+void GLWindow::setR2(float v)
+{
+    m_r2 = v;
+    m_uniformsDirty = true;
+    update();
+}
+
+static const char *vertexShaderSource =
+    "layout(location = 0) in vec4 vertex;\n"
+    "layout(location = 1) in vec3 normal;\n"
+    "out vec3 vert;\n"
+    "out vec3 vertNormal;\n"
+    "out vec3 color;\n"
+    "uniform mat4 projMatrix;\n"
+    "uniform mat4 camMatrix;\n"
+    "uniform mat4 worldMatrix;\n"
+    "uniform mat4 myMatrix;\n"
+    "uniform sampler2D sampler;\n"
+    "void main() {\n"
+    "   ivec2 pos = ivec2(gl_InstanceID % 32, gl_InstanceID / 32);\n"
+    "   vec2 t = vec2(float(-16 + pos.x) * 0.8, float(-18 + pos.y) * 0.6);\n"
+    "   float val = 2.0 * length(texelFetch(sampler, pos, 0).rgb);\n"
+    "   mat4 wm = myMatrix * mat4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, t.x, t.y, val, 1) * worldMatrix;\n"
+    "   color = texelFetch(sampler, pos, 0).rgb * vec3(0.4, 1.0, 0.0);\n"
+    "   vert = vec3(wm * vertex);\n"
+    "   vertNormal = mat3(transpose(inverse(wm))) * normal;\n"
+    "   gl_Position = projMatrix * camMatrix * wm * vertex;\n"
+    "}\n";
+
+static const char *fragmentShaderSource =
+    "in highp vec3 vert;\n"
+    "in highp vec3 vertNormal;\n"
+    "in highp vec3 color;\n"
+    "out highp vec4 fragColor;\n"
+    "uniform highp vec3 lightPos;\n"
+    "void main() {\n"
+    "   highp vec3 L = normalize(lightPos - vert);\n"
+    "   highp float NL = max(dot(normalize(vertNormal), L), 0.0);\n"
+    "   highp vec3 col = clamp(color * 0.2 + color * 0.8 * NL, 0.0, 1.0);\n"
+    "   fragColor = vec4(col, 1.0);\n"
+    "}\n";
+
+QByteArray versionedShaderCode(const char *src)
+{
+    QByteArray versionedSrc;
+
+    if (QOpenGLContext::currentContext()->isOpenGLES())
+        versionedSrc.append(QByteArrayLiteral("#version 300 es\n"));
+    else
+        versionedSrc.append(QByteArrayLiteral("#version 330\n"));
+
+    versionedSrc.append(src);
+    return versionedSrc;
+}
+
+void GLWindow::initializeGL()
+{
+    QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
+    
+    QSurfaceFormat format = QOpenGLContext::currentContext()->format();
+    qDebug() << QString("OpenGL Version:%1.%2").arg(format.majorVersion()).arg(format.minorVersion());
+
+    QImage img(":/qtlogo.png");
+    Q_ASSERT(!img.isNull());
+    delete m_texture;
+    m_texture = new QOpenGLTexture(img.scaled(32, 36).mirrored());
+
+    delete m_program;
+    m_program = new QOpenGLShaderProgram;
+    // Prepend the correct version directive to the sources. The rest is the
+    // same, thanks to the common GLSL syntax.
+    m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, versionedShaderCode(vertexShaderSource));
+    m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, versionedShaderCode(fragmentShaderSource));
+    m_program->link();
+
+    m_projMatrixLoc = m_program->uniformLocation("projMatrix");
+    m_camMatrixLoc = m_program->uniformLocation("camMatrix");
+    m_worldMatrixLoc = m_program->uniformLocation("worldMatrix");
+    m_myMatrixLoc = m_program->uniformLocation("myMatrix");
+    m_lightPosLoc = m_program->uniformLocation("lightPos");
+
+    // Create a VAO. Not strictly required for ES 3, but it is for plain OpenGL.
+    delete m_vao;
+    m_vao = new QOpenGLVertexArrayObject;
+    if (m_vao->create())
+        m_vao->bind();
+
+    m_program->bind();
+    delete m_vbo;
+    m_vbo = new QOpenGLBuffer;
+    m_vbo->create();
+    m_vbo->bind();
+    m_vbo->allocate(m_logo.constData(), m_logo.count() * sizeof(GLfloat));
+    f->glEnableVertexAttribArray(0);
+    f->glEnableVertexAttribArray(1);
+    f->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat),
+                             nullptr);
+    f->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat),
+                             reinterpret_cast<void *>(3 * sizeof(GLfloat)));
+    m_vbo->release();
+
+    f->glEnable(GL_DEPTH_TEST);
+    f->glEnable(GL_CULL_FACE);
+}
+
+void GLWindow::resizeGL(int w, int h)
+{
+    m_proj.setToIdentity();
+    m_proj.perspective(45.0f, GLfloat(w) / h, 0.01f, 100.0f);
+    m_uniformsDirty = true;
+}
+
+void GLWindow::paintGL()
+{
+    // Now use QOpenGLExtraFunctions instead of QOpenGLFunctions as we want to
+    // do more than what GL(ES) 2.0 offers.
+    QOpenGLExtraFunctions *f = QOpenGLContext::currentContext()->extraFunctions();
+
+    f->glClearColor(0, 0, 0, 1);
+    f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+    m_program->bind();
+    m_texture->bind();
+
+    if (m_uniformsDirty) {
+        m_uniformsDirty = false;
+        QMatrix4x4 camera;
+        camera.lookAt(m_eye, m_eye + m_target, QVector3D(0, 1, 0));
+        m_program->setUniformValue(m_projMatrixLoc, m_proj);
+        m_program->setUniformValue(m_camMatrixLoc, camera);
+        QMatrix4x4 wm = m_world;
+        wm.rotate(m_r, 1, 1, 0);
+        m_program->setUniformValue(m_worldMatrixLoc, wm);
+        QMatrix4x4 mm;
+        mm.setToIdentity();
+        mm.rotate(-m_r2, 1, 0, 0);
+        m_program->setUniformValue(m_myMatrixLoc, mm);
+        m_program->setUniformValue(m_lightPosLoc, QVector3D(0, 0, 70));
+    }
+
+    // Now call a function introduced in OpenGL 3.1 / OpenGL ES 3.0. We
+    // requested a 3.3 or ES 3.0 context, so we know this will work.
+    f->glDrawArraysInstanced(GL_TRIANGLES, 0, m_logo.vertexCount(), 32 * 36);
+}
diff --git a/hellogl/glwindow.h b/hellogl/glwindow.h
new file mode 100644
index 0000000000000000000000000000000000000000..95c6a9b568f9107dea2179a6a4128193a5638d41
--- /dev/null
+++ b/hellogl/glwindow.h
@@ -0,0 +1,61 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef GLWIDGET_H
+#define GLWIDGET_H
+
+#include <QOpenGLWindow>
+#include <QMatrix4x4>
+#include <QVector3D>
+#include "logo.h"
+
+QT_FORWARD_DECLARE_CLASS(QOpenGLTexture)
+QT_FORWARD_DECLARE_CLASS(QOpenGLShaderProgram)
+QT_FORWARD_DECLARE_CLASS(QOpenGLBuffer)
+QT_FORWARD_DECLARE_CLASS(QOpenGLVertexArrayObject)
+
+class GLWindow : public QOpenGLWindow
+{
+    Q_OBJECT
+    Q_PROPERTY(float z READ z WRITE setZ)
+    Q_PROPERTY(float r READ r WRITE setR)
+    Q_PROPERTY(float r2 READ r2 WRITE setR2)
+
+public:
+    GLWindow();
+    ~GLWindow();
+
+    void initializeGL();
+    void resizeGL(int w, int h);
+    void paintGL();
+
+    float z() const { return m_eye.z(); }
+    void setZ(float v);
+
+    float r() const { return m_r; }
+    void setR(float v);
+    float r2() const { return m_r2; }
+    void setR2(float v);
+private slots:
+    void startSecondStage();
+private:
+    QOpenGLTexture *m_texture = nullptr;
+    QOpenGLShaderProgram *m_program = nullptr;
+    QOpenGLBuffer *m_vbo = nullptr;
+    QOpenGLVertexArrayObject *m_vao = nullptr;
+    Logo m_logo;
+    int m_projMatrixLoc = 0;
+    int m_camMatrixLoc = 0;
+    int m_worldMatrixLoc = 0;
+    int m_myMatrixLoc = 0;
+    int m_lightPosLoc = 0;
+    QMatrix4x4 m_proj;
+    QMatrix4x4 m_world;
+    QVector3D m_eye;
+    QVector3D m_target = {0, 0, -1};
+    bool m_uniformsDirty = true;
+    float m_r = 0;
+    float m_r2 = 0;
+};
+
+#endif
diff --git a/hellogl/hellogles3.qrc b/hellogl/hellogles3.qrc
new file mode 100644
index 0000000000000000000000000000000000000000..f3a09780843359aad7acc9257cdd3875016f9b01
--- /dev/null
+++ b/hellogl/hellogles3.qrc
@@ -0,0 +1,5 @@
+<RCC>
+  <qresource>
+    <file>qtlogo.png</file>
+  </qresource>
+</RCC>
diff --git a/hellogl/logo.cpp b/hellogl/logo.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b924ba043d06b360a5da38ce6415e69e26461349
--- /dev/null
+++ b/hellogl/logo.cpp
@@ -0,0 +1,103 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "logo.h"
+#include <qmath.h>
+
+Logo::Logo()
+{
+    m_data.resize(2500 * 6);
+
+    const GLfloat x1 = +0.06f;
+    const GLfloat y1 = -0.14f;
+    const GLfloat x2 = +0.14f;
+    const GLfloat y2 = -0.06f;
+    const GLfloat x3 = +0.08f;
+    const GLfloat y3 = +0.00f;
+    const GLfloat x4 = +0.30f;
+    const GLfloat y4 = +0.22f;
+
+    quad(x1, y1, x2, y2, y2, x2, y1, x1);
+    quad(x3, y3, x4, y4, y4, x4, y3, x3);
+
+    extrude(x1, y1, x2, y2);
+    extrude(x2, y2, y2, x2);
+    extrude(y2, x2, y1, x1);
+    extrude(y1, x1, x1, y1);
+    extrude(x3, y3, x4, y4);
+    extrude(x4, y4, y4, x4);
+    extrude(y4, x4, y3, x3);
+
+    const int NumSectors = 100;
+
+    for (int i = 0; i < NumSectors; ++i) {
+        GLfloat angle = (i * 2 * M_PI) / NumSectors;
+        GLfloat angleSin = qSin(angle);
+        GLfloat angleCos = qCos(angle);
+        const GLfloat x5 = 0.30f * angleSin;
+        const GLfloat y5 = 0.30f * angleCos;
+        const GLfloat x6 = 0.20f * angleSin;
+        const GLfloat y6 = 0.20f * angleCos;
+
+        angle = ((i + 1) * 2 * M_PI) / NumSectors;
+        angleSin = qSin(angle);
+        angleCos = qCos(angle);
+        const GLfloat x7 = 0.20f * angleSin;
+        const GLfloat y7 = 0.20f * angleCos;
+        const GLfloat x8 = 0.30f * angleSin;
+        const GLfloat y8 = 0.30f * angleCos;
+
+        quad(x5, y5, x6, y6, x7, y7, x8, y8);
+
+        extrude(x6, y6, x7, y7);
+        extrude(x8, y8, x5, y5);
+    }
+}
+
+void Logo::add(const QVector3D &v, const QVector3D &n)
+{
+    GLfloat *p = m_data.data() + m_count;
+    *p++ = v.x();
+    *p++ = v.y();
+    *p++ = v.z();
+    *p++ = n.x();
+    *p++ = n.y();
+    *p++ = n.z();
+    m_count += 6;
+}
+
+void Logo::quad(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2, GLfloat x3, GLfloat y3, GLfloat x4, GLfloat y4)
+{
+    QVector3D n = QVector3D::normal(QVector3D(x4 - x1, y4 - y1, 0.0f), QVector3D(x2 - x1, y2 - y1, 0.0f));
+
+    add(QVector3D(x1, y1, -0.05f), n);
+    add(QVector3D(x4, y4, -0.05f), n);
+    add(QVector3D(x2, y2, -0.05f), n);
+
+    add(QVector3D(x3, y3, -0.05f), n);
+    add(QVector3D(x2, y2, -0.05f), n);
+    add(QVector3D(x4, y4, -0.05f), n);
+
+    n = QVector3D::normal(QVector3D(x1 - x4, y1 - y4, 0.0f), QVector3D(x2 - x4, y2 - y4, 0.0f));
+
+    add(QVector3D(x4, y4, 0.05f), n);
+    add(QVector3D(x1, y1, 0.05f), n);
+    add(QVector3D(x2, y2, 0.05f), n);
+
+    add(QVector3D(x2, y2, 0.05f), n);
+    add(QVector3D(x3, y3, 0.05f), n);
+    add(QVector3D(x4, y4, 0.05f), n);
+}
+
+void Logo::extrude(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2)
+{
+    QVector3D n = QVector3D::normal(QVector3D(0.0f, 0.0f, -0.1f), QVector3D(x2 - x1, y2 - y1, 0.0f));
+
+    add(QVector3D(x1, y1, +0.05f), n);
+    add(QVector3D(x1, y1, -0.05f), n);
+    add(QVector3D(x2, y2, +0.05f), n);
+
+    add(QVector3D(x2, y2, -0.05f), n);
+    add(QVector3D(x2, y2, +0.05f), n);
+    add(QVector3D(x1, y1, -0.05f), n);
+}
diff --git a/hellogl/logo.h b/hellogl/logo.h
new file mode 100644
index 0000000000000000000000000000000000000000..0eed3f6978dc4a44ad0ac1bb8cb842d8385ae271
--- /dev/null
+++ b/hellogl/logo.h
@@ -0,0 +1,28 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef LOGO_H
+#define LOGO_H
+
+#include <qopengl.h>
+#include <QList>
+#include <QVector3D>
+
+class Logo
+{
+public:
+    Logo();
+    const GLfloat *constData() const { return m_data.constData(); }
+    int count() const { return m_count; }
+    int vertexCount() const { return m_count / 6; }
+
+private:
+    void quad(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2, GLfloat x3, GLfloat y3, GLfloat x4, GLfloat y4);
+    void extrude(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2);
+    void add(const QVector3D &v, const QVector3D &n);
+
+    QList<GLfloat> m_data;
+    int m_count = 0;
+};
+
+#endif // LOGO_H
diff --git a/hellogl/main.cpp b/hellogl/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b75702e9622fe35f535eed572a8d6f0647577ab5
--- /dev/null
+++ b/hellogl/main.cpp
@@ -0,0 +1,46 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QGuiApplication>
+#include <QSurfaceFormat>
+#include <QOpenGLContext>
+
+#include "glwindow.h"
+
+// This example demonstrates easy, cross-platform usage of OpenGL ES 3.0 functions via
+// QOpenGLExtraFunctions in an application that works identically on desktop platforms
+// with OpenGL 3.3 and mobile/embedded devices with OpenGL ES 3.0.
+
+// The code is always the same, with the exception of two places: (1) the OpenGL context
+// creation has to have a sufficiently high version number for the features that are in
+// use, and (2) the shader code's version directive is different.
+
+int main(int argc, char *argv[])
+{
+    QGuiApplication app(argc, argv);
+
+    QSurfaceFormat fmt;
+    fmt.setDepthBufferSize(24);
+
+    /*
+        Use the default WebGL version. Should be
+        OpenGL ES 3.0 / WebGL 2 on recent Qt versions.
+
+    // Request OpenGL 3.3 core or OpenGL ES 3.0.
+    if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL) {
+        qDebug("Requesting 3.3 core context");
+        fmt.setVersion(3, 3);
+        fmt.setProfile(QSurfaceFormat::CoreProfile);
+    } else {
+        qDebug("Requesting 3.0 context");
+        fmt.setVersion(3, 0);
+    }
+*/
+
+    QSurfaceFormat::setDefaultFormat(fmt);
+
+    GLWindow glWindow;
+    glWindow.show();
+
+    return app.exec();
+}
diff --git a/hellogl/qtlogo.png b/hellogl/qtlogo.png
new file mode 100644
index 0000000000000000000000000000000000000000..9cb2e01d3895ed7d1a81e91ed5393b474d041671
Binary files /dev/null and b/hellogl/qtlogo.png differ