summaryrefslogtreecommitdiffstats
path: root/tests/manual/examples/opengl
diff options
context:
space:
mode:
Diffstat (limited to 'tests/manual/examples/opengl')
-rw-r--r--tests/manual/examples/opengl/computegles31/CMakeLists.txt49
-rw-r--r--tests/manual/examples/opengl/computegles31/Qt-logo-medium.pngbin0 -> 24817 bytes
-rw-r--r--tests/manual/examples/opengl/computegles31/computegles31.pro11
-rw-r--r--tests/manual/examples/opengl/computegles31/computegles31.qrc5
-rw-r--r--tests/manual/examples/opengl/computegles31/glwindow.cpp372
-rw-r--r--tests/manual/examples/opengl/computegles31/glwindow.h63
-rw-r--r--tests/manual/examples/opengl/computegles31/main.cpp101
-rw-r--r--tests/manual/examples/opengl/contextinfo/CMakeLists.txt39
-rw-r--r--tests/manual/examples/opengl/contextinfo/contextinfo.pro14
-rw-r--r--tests/manual/examples/opengl/contextinfo/main.cpp25
-rw-r--r--tests/manual/examples/opengl/contextinfo/renderwindow.cpp191
-rw-r--r--tests/manual/examples/opengl/contextinfo/renderwindow.h48
-rw-r--r--tests/manual/examples/opengl/contextinfo/widget.cpp360
-rw-r--r--tests/manual/examples/opengl/contextinfo/widget.h46
-rw-r--r--tests/manual/examples/opengl/hellowindow/CMakeLists.txt39
-rw-r--r--tests/manual/examples/opengl/hellowindow/hellowindow.cpp305
-rw-r--r--tests/manual/examples/opengl/hellowindow/hellowindow.h77
-rw-r--r--tests/manual/examples/opengl/hellowindow/hellowindow.pro9
-rw-r--r--tests/manual/examples/opengl/hellowindow/main.cpp125
-rw-r--r--tests/manual/examples/opengl/paintedwindow/CMakeLists.txt37
-rw-r--r--tests/manual/examples/opengl/paintedwindow/main.cpp18
-rw-r--r--tests/manual/examples/opengl/paintedwindow/paintedwindow.cpp185
-rw-r--r--tests/manual/examples/opengl/paintedwindow/paintedwindow.h62
-rw-r--r--tests/manual/examples/opengl/paintedwindow/paintedwindow.pro8
-rw-r--r--tests/manual/examples/opengl/qopenglwidget/CMakeLists.txt53
-rw-r--r--tests/manual/examples/opengl/qopenglwidget/bubble.cpp99
-rw-r--r--tests/manual/examples/opengl/qopenglwidget/bubble.h39
-rw-r--r--tests/manual/examples/opengl/qopenglwidget/glwidget.cpp540
-rw-r--r--tests/manual/examples/opengl/qopenglwidget/glwidget.h85
-rw-r--r--tests/manual/examples/opengl/qopenglwidget/main.cpp42
-rw-r--r--tests/manual/examples/opengl/qopenglwidget/mainwindow.cpp188
-rw-r--r--tests/manual/examples/opengl/qopenglwidget/mainwindow.h39
-rw-r--r--tests/manual/examples/opengl/qopenglwidget/qopenglwidget.pro15
-rw-r--r--tests/manual/examples/opengl/qopenglwidget/qt.pngbin0 -> 2991 bytes
-rw-r--r--tests/manual/examples/opengl/qopenglwidget/texture.qrc5
-rw-r--r--tests/manual/examples/opengl/qopenglwindow/CMakeLists.txt49
-rw-r--r--tests/manual/examples/opengl/qopenglwindow/background.frag30
-rw-r--r--tests/manual/examples/opengl/qopenglwindow/background_renderer.cpp167
-rw-r--r--tests/manual/examples/opengl/qopenglwindow/background_renderer.h46
-rw-r--r--tests/manual/examples/opengl/qopenglwindow/main.cpp154
-rw-r--r--tests/manual/examples/opengl/qopenglwindow/qopenglwindow.pro16
-rw-r--r--tests/manual/examples/opengl/qopenglwindow/shaders.qrc5
42 files changed, 3761 insertions, 0 deletions
diff --git a/tests/manual/examples/opengl/computegles31/CMakeLists.txt b/tests/manual/examples/opengl/computegles31/CMakeLists.txt
new file mode 100644
index 0000000000..32426152aa
--- /dev/null
+++ b/tests/manual/examples/opengl/computegles31/CMakeLists.txt
@@ -0,0 +1,49 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(computegles31 LANGUAGES CXX)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/opengl/computegles31")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui OpenGL)
+
+qt_standard_project_setup()
+
+qt_add_executable(computegles31
+ glwindow.cpp glwindow.h
+ main.cpp
+)
+
+set_target_properties(computegles31 PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(computegles31 PRIVATE
+ Qt6::Core
+ Qt6::Gui
+ Qt6::OpenGL
+)
+
+# Resources:
+set(computegles31_resource_files
+ "Qt-logo-medium.png"
+)
+
+qt_add_resources(computegles31 "computegles31"
+ PREFIX
+ "/"
+ FILES
+ ${computegles31_resource_files}
+)
+
+install(TARGETS computegles31
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/tests/manual/examples/opengl/computegles31/Qt-logo-medium.png b/tests/manual/examples/opengl/computegles31/Qt-logo-medium.png
new file mode 100644
index 0000000000..a1ca1f1830
--- /dev/null
+++ b/tests/manual/examples/opengl/computegles31/Qt-logo-medium.png
Binary files differ
diff --git a/tests/manual/examples/opengl/computegles31/computegles31.pro b/tests/manual/examples/opengl/computegles31/computegles31.pro
new file mode 100644
index 0000000000..8d9b00ba83
--- /dev/null
+++ b/tests/manual/examples/opengl/computegles31/computegles31.pro
@@ -0,0 +1,11 @@
+QT += opengl
+
+HEADERS = $$PWD/glwindow.h
+
+SOURCES = $$PWD/glwindow.cpp \
+ $$PWD/main.cpp
+
+RESOURCES += computegles31.qrc
+
+target.path = $$[QT_INSTALL_EXAMPLES]/opengl/computegles31
+INSTALLS += target
diff --git a/tests/manual/examples/opengl/computegles31/computegles31.qrc b/tests/manual/examples/opengl/computegles31/computegles31.qrc
new file mode 100644
index 0000000000..b99eb0a82e
--- /dev/null
+++ b/tests/manual/examples/opengl/computegles31/computegles31.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>Qt-logo-medium.png</file>
+ </qresource>
+</RCC>
diff --git a/tests/manual/examples/opengl/computegles31/glwindow.cpp b/tests/manual/examples/opengl/computegles31/glwindow.cpp
new file mode 100644
index 0000000000..98533e714e
--- /dev/null
+++ b/tests/manual/examples/opengl/computegles31/glwindow.cpp
@@ -0,0 +1,372 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "glwindow.h"
+#include <QImage>
+#include <QOpenGLShaderProgram>
+#include <QOpenGLContext>
+#include <QOpenGLFunctions>
+#include <QOpenGLExtraFunctions>
+#include <QOpenGLVertexArrayObject>
+#include <QtGui/qopengl.h>
+#include <QDebug>
+#include <QTimer>
+#include <math.h>
+
+#ifndef GL_READ_WRITE
+#define GL_READ_WRITE 0x88BA
+#endif
+
+#ifndef GL_RGBA8
+#define GL_RGBA8 0x8058
+#endif
+
+#ifndef GL_SHADER_IMAGE_ACCESS_BARRIER_BIT
+#define GL_SHADER_IMAGE_ACCESS_BARRIER_BIT 0x00000020
+#endif
+
+GLWindow::GLWindow()
+{
+ const float animationStart = 0.0;
+ const float animationEnd = 10.0;
+ const float animationLength = 1000;
+
+ m_animationGroup = new QSequentialAnimationGroup(this);
+ m_animationGroup->setLoopCount(-1);
+
+ m_animationForward = new QPropertyAnimation(this, QByteArrayLiteral("blurRadius"));
+ m_animationForward->setStartValue(animationStart);
+ m_animationForward->setEndValue(animationEnd);
+ m_animationForward->setDuration(animationLength);
+ m_animationGroup->addAnimation(m_animationForward);
+
+ m_animationBackward = new QPropertyAnimation(this, QByteArrayLiteral("blurRadius"));
+ m_animationBackward->setStartValue(animationEnd);
+ m_animationBackward->setEndValue(animationStart);
+ m_animationBackward->setDuration(animationLength);
+ m_animationGroup->addAnimation(m_animationBackward);
+
+ m_animationGroup->start();
+}
+
+GLWindow::~GLWindow()
+{
+ makeCurrent();
+ delete m_texImageInput;
+ delete m_texImageProcessed;
+ delete m_texImageTmp;
+ delete m_shaderDisplay;
+ delete m_shaderComputeH;
+ delete m_shaderComputeV;
+ delete m_animationGroup;
+ delete m_animationForward;
+ delete m_animationBackward;
+ delete m_vao;
+}
+
+void GLWindow::setBlurRadius(float blurRadius)
+{
+ int radius = int(blurRadius);
+ if (radius != m_blurRadius) {
+ m_blurRadius = radius;
+ update();
+ }
+}
+
+void GLWindow::setAnimating(bool animate)
+{
+ m_animate = animate;
+ if (animate)
+ m_animationGroup->start();
+ else
+ m_animationGroup->stop();
+}
+
+void GLWindow::keyPressEvent(QKeyEvent *e)
+{
+ if (e->key() == Qt::Key_Space) { // pause
+ setAnimating(!m_animate);
+ }
+ update();
+}
+
+
+
+
+static const char *vsDisplaySource =
+ "const vec4 vertices[4] = vec4[4] (\n"
+ " vec4( -1.0, 1.0, 0.0, 1.0),\n"
+ " vec4( -1.0, -1.0, 0.0, 1.0),\n"
+ " vec4( 1.0, 1.0, 0.0, 1.0),\n"
+ " vec4( 1.0, -1.0, 0.0, 1.0)\n"
+ ");\n"
+ "const vec2 texCoords[4] = vec2[4] (\n"
+ " vec2( 0.0, 1.0),\n"
+ " vec2( 0.0, 0.0),\n"
+ " vec2( 1.0, 1.0),\n"
+ " vec2( 1.0, 0.0)\n"
+ ");\n"
+ "out vec2 texCoord;\n"
+ "uniform mat4 matProjection;\n"
+ "uniform vec2 imageRatio;\n"
+ "void main() {\n"
+ " gl_Position = matProjection * ( vertices[gl_VertexID] * vec4(imageRatio,0,1) );\n"
+ " texCoord = texCoords[gl_VertexID];\n"
+ "}\n";
+
+static const char *fsDisplaySource =
+ "in lowp vec2 texCoord; \n"
+ "uniform sampler2D samImage; \n"
+ "layout(location = 0) out lowp vec4 color;\n"
+ "void main() {\n"
+ " lowp vec4 texColor = texture(samImage,texCoord);\n"
+ " color = vec4(texColor.rgb, 1.0);\n"
+ "}\n";
+
+static const char *csComputeSourceV =
+ "#define COMPUTEPATCHSIZE 10 // Setting this to 10 to comply with MAX_COMPUTE_WORK_GROUP_INVOCATIONS for both OpenGL and OpenGLES - see QTBUG-79374 \n"
+ "#define IMGFMT rgba8 \n"
+ "layout (local_size_x = COMPUTEPATCHSIZE, local_size_y = COMPUTEPATCHSIZE) in;\n"
+ "layout(binding=0, IMGFMT) uniform readonly highp image2D inputImage; // Use a sampler to improve performance \n"
+ "layout(binding=1, IMGFMT) uniform writeonly highp image2D resultImage;\n"
+ "uniform int radius;\n"
+ "const float cutoff = 2.2;\n"
+
+ "float expFactor() { // a function, otherwise MESA produces error: initializer of global variable `expFactor' must be a constant expression\n"
+ " float sigma = clamp(float(radius) / cutoff,0.02,100.0);\n"
+ " return 1.0 / (2.0 * sigma * sigma);\n"
+ "}\n"
+
+ "float gaussian(float distance, float expfactor) {\n"
+ " return exp( -(distance * distance) * expfactor);\n"
+ "}\n"
+
+ "void main() {\n"
+ " ivec2 imgSize = imageSize(resultImage);\n"
+ " int x = int(gl_GlobalInvocationID.x);\n"
+ " int y = int(gl_GlobalInvocationID.y);\n"
+ " if ( (x >= imgSize.x) || (y >= imgSize.y) ) return;\n"
+ " vec4 sumPixels = vec4(0.0);\n"
+ " float sumWeights = 0.0;\n"
+ " int left = clamp(x - radius, 0, imgSize.x - 1);\n"
+ " int right = clamp(x + radius, 0, imgSize.x - 1);\n"
+ " int top = clamp(y - radius, 0, imgSize.y - 1);\n"
+ " int bottom = clamp(y + radius, 0, imgSize.y - 1);\n"
+ " float expfactor = expFactor();\n"
+ " for (int iY = top; iY <= bottom; iY++) {\n"
+ " float dy = float(abs(iY - y));\n"
+ " vec4 imgValue = imageLoad(inputImage, ivec2(x,iY));\n"
+ " float weight = gaussian(dy, expfactor);\n"
+ " sumWeights += weight;\n"
+ " sumPixels += (imgValue * weight);\n"
+ " }\n"
+ " sumPixels /= sumWeights;\n"
+ " imageStore(resultImage, ivec2(x,y), sumPixels);\n"
+ "}\n";
+
+static const char *csComputeSourceH =
+ "#define COMPUTEPATCHSIZE 10 \n"
+ "#define IMGFMT rgba8 \n"
+ "layout (local_size_x = COMPUTEPATCHSIZE, local_size_y = COMPUTEPATCHSIZE) in;\n"
+ "layout(binding=0, IMGFMT) uniform readonly highp image2D inputImage; // Use a sampler to improve performance \n"
+ "layout(binding=1, IMGFMT) uniform writeonly highp image2D resultImage;\n"
+ "uniform int radius;\n"
+ "const float cutoff = 2.2;\n"
+
+ "float expFactor() { // a function, otherwise MESA produces error: initializer of global variable `expFactor' must be a constant expression\n"
+ " float sigma = clamp(float(radius) / cutoff,0.02,100.0);\n"
+ " return 1.0 / (2.0 * sigma * sigma);\n"
+ "}\n"
+
+ "float gaussian(float distance, float expfactor) {\n"
+ " return exp( -(distance * distance) * expfactor);\n"
+ "}\n"
+
+ "void main() {\n"
+ " ivec2 imgSize = imageSize(resultImage);\n"
+ " int x = int(gl_GlobalInvocationID.x);\n"
+ " int y = int(gl_GlobalInvocationID.y);\n"
+ " if ( (x >= imgSize.x) || (y >= imgSize.y) ) return;\n"
+ " vec4 sumPixels = vec4(0.0);\n"
+ " float sumWeights = 0.0;\n"
+ " int left = clamp(x - radius, 0, imgSize.x - 1);\n"
+ " int right = clamp(x + radius, 0, imgSize.x - 1);\n"
+ " int top = clamp(y - radius, 0, imgSize.y - 1);\n"
+ " int bottom = clamp(y + radius, 0, imgSize.y - 1);\n"
+ " float expfactor = expFactor();\n"
+ " for (int iX = left; iX <= right; iX++) {\n"
+ " float dx = float(abs(iX - x));\n"
+ " vec4 imgValue = imageLoad(inputImage, ivec2(iX,y));\n"
+ " float weight = gaussian(dx, expfactor);\n"
+ " sumWeights += weight;\n"
+ " sumPixels += (imgValue * weight);\n"
+ " }\n"
+ " sumPixels /= sumWeights;\n"
+ " imageStore(resultImage, ivec2(x,y), sumPixels);\n"
+ "}\n";
+
+
+
+QByteArray versionedShaderCode(const char *src)
+{
+ QByteArray versionedSrc;
+
+ if (QOpenGLContext::currentContext()->isOpenGLES())
+ versionedSrc.append(QByteArrayLiteral("#version 310 es\n"));
+ else
+ versionedSrc.append(QByteArrayLiteral("#version 430 core\n"));
+
+ versionedSrc.append(src);
+ return versionedSrc;
+}
+
+void computeProjection(int winWidth, int winHeight, int imgWidth, int imgHeight, QMatrix4x4 &outProjection, QSizeF &outQuadSize)
+{
+ float ratioImg = float(imgWidth) / float(imgHeight);
+ float ratioCanvas = float(winWidth) / float(winHeight);
+
+ float correction = ratioImg / ratioCanvas;
+ float rescaleFactor = 1.0f;
+ float quadWidth = 1.0f;
+ float quadHeight = 1.0f;
+
+ if (correction < 1.0f) // canvas larger than image -- height = 1.0, vertical black bands
+ {
+ quadHeight = 1.0f;
+ quadWidth = 1.0f * ratioImg;
+ rescaleFactor = ratioCanvas;
+ correction = 1.0f / rescaleFactor;
+ }
+ else // image larger than canvas -- width = 1.0, horizontal black bands
+ {
+ quadWidth = 1.0f;
+ quadHeight = 1.0f / ratioImg;
+ correction = 1.0f / ratioCanvas;
+ }
+
+ const float frustumWidth = 1.0f * rescaleFactor;
+ const float frustumHeight = 1.0f * rescaleFactor * correction;
+
+ outProjection = QMatrix4x4();
+ outProjection.ortho(
+ -frustumWidth,
+ frustumWidth,
+ -frustumHeight,
+ frustumHeight,
+ -1.0f,
+ 1.0f);
+ outQuadSize = QSizeF(quadWidth,quadHeight);
+}
+
+void GLWindow::initializeGL()
+{
+ QOpenGLContext *ctx = QOpenGLContext::currentContext();
+ qDebug() << "Got a "
+ << ctx->format().majorVersion()
+ << "."
+ << ctx->format().minorVersion()
+ << ((ctx->format().renderableType() == QSurfaceFormat::OpenGLES) ? (" GLES") : (" GL"))
+ << " context";
+
+ QImage img(":/Qt-logo-medium.png");
+ Q_ASSERT(!img.isNull());
+ delete m_texImageInput;
+ m_texImageInput = new QOpenGLTexture(img.convertToFormat(QImage::Format_RGBA8888).mirrored());
+
+ delete m_texImageTmp;
+ m_texImageTmp = new QOpenGLTexture(QOpenGLTexture::Target2D);
+ m_texImageTmp->setFormat(m_texImageInput->format());
+ m_texImageTmp->setSize(m_texImageInput->width(),m_texImageInput->height());
+ m_texImageTmp->allocateStorage(QOpenGLTexture::RGBA,QOpenGLTexture::UInt8); // WTF?
+
+ delete m_texImageProcessed;
+ m_texImageProcessed = new QOpenGLTexture(QOpenGLTexture::Target2D);
+ m_texImageProcessed->setFormat(m_texImageInput->format());
+ m_texImageProcessed->setSize(m_texImageInput->width(),m_texImageInput->height());
+ m_texImageProcessed->allocateStorage(QOpenGLTexture::RGBA,QOpenGLTexture::UInt8);
+
+ m_texImageProcessed->setMagnificationFilter(QOpenGLTexture::Linear);
+ m_texImageProcessed->setMinificationFilter(QOpenGLTexture::Linear);
+ m_texImageProcessed->setWrapMode(QOpenGLTexture::ClampToEdge);
+
+ delete m_shaderDisplay;
+ m_shaderDisplay = new QOpenGLShaderProgram;
+ // Prepend the correct version directive to the sources. The rest is the
+ // same, thanks to the common GLSL syntax.
+ m_shaderDisplay->addShaderFromSourceCode(QOpenGLShader::Vertex, versionedShaderCode(vsDisplaySource));
+ m_shaderDisplay->addShaderFromSourceCode(QOpenGLShader::Fragment, versionedShaderCode(fsDisplaySource));
+ m_shaderDisplay->link();
+
+ delete m_shaderComputeV;
+ m_shaderComputeV = new QOpenGLShaderProgram;
+ m_shaderComputeV->addShaderFromSourceCode(QOpenGLShader::Compute, versionedShaderCode(csComputeSourceV));
+ m_shaderComputeV->link();
+
+ delete m_shaderComputeH;
+ m_shaderComputeH = new QOpenGLShaderProgram;
+ m_shaderComputeH->addShaderFromSourceCode(QOpenGLShader::Compute, versionedShaderCode(csComputeSourceH));
+ m_shaderComputeH->link();
+
+ // Create a VAO. Not strictly required for ES 3, but it is for plain OpenGL core context.
+ m_vao = new QOpenGLVertexArrayObject;
+ m_vao->create();
+}
+
+void GLWindow::resizeGL(int w, int h)
+{
+ computeProjection(w,h,m_texImageInput->width(),m_texImageInput->height(),m_proj,m_quadSize);
+}
+
+QSize getWorkGroups(int workGroupSize, const QSize &imageSize)
+{
+ int x = imageSize.width();
+ x = (x % workGroupSize) ? (x / workGroupSize) + 1 : (x / workGroupSize);
+ int y = imageSize.height();
+ y = (y % workGroupSize) ? (y / workGroupSize) + 1 : (y / workGroupSize);
+ return QSize(x,y);
+}
+
+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();
+
+
+ // Process input image
+ QSize workGroups = getWorkGroups(10, QSize(m_texImageInput->width(), m_texImageInput->height()));
+ // Pass 1
+ f->glBindImageTexture(0, m_texImageInput->textureId(), 0, 0, 0, GL_READ_WRITE, GL_RGBA8);
+ f->glBindImageTexture(1, m_texImageTmp->textureId(), 0, 0, 0, GL_READ_WRITE, GL_RGBA8);
+ m_shaderComputeV->bind();
+ m_shaderComputeV->setUniformValue("radius",m_blurRadius);
+ f->glDispatchCompute(workGroups.width(),workGroups.height(),1);
+ f->glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
+ m_shaderComputeV->release();
+ // Pass 2
+ f->glBindImageTexture(0, m_texImageTmp->textureId(), 0, 0, 0, GL_READ_WRITE, GL_RGBA8);
+ f->glBindImageTexture(1, m_texImageProcessed->textureId(), 0, 0, 0, GL_READ_WRITE, GL_RGBA8);
+ m_shaderComputeH->bind();
+ m_shaderComputeH->setUniformValue("radius",m_blurRadius);
+ f->glDispatchCompute(workGroups.width(),workGroups.height(),1);
+ f->glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
+ m_shaderComputeH->release();
+ // Compute cleanup
+ f->glBindImageTexture(0, 0, 0, 0, 0, GL_READ_WRITE, GL_RGBA8);
+ f->glBindImageTexture(1, 0, 0, 0, 0, GL_READ_WRITE, GL_RGBA8);
+
+ // Display processed image
+ f->glClearColor(0, 0, 0, 1);
+ f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ m_texImageProcessed->bind(0);
+ m_shaderDisplay->bind();
+ m_shaderDisplay->setUniformValue("matProjection",m_proj);
+ m_shaderDisplay->setUniformValue("imageRatio",m_quadSize);
+ m_shaderDisplay->setUniformValue("samImage",0);
+ m_vao->bind();
+ f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+ m_vao->release();
+ m_shaderDisplay->release();
+ m_texImageProcessed->release(0);
+}
+
diff --git a/tests/manual/examples/opengl/computegles31/glwindow.h b/tests/manual/examples/opengl/computegles31/glwindow.h
new file mode 100644
index 0000000000..268647a7aa
--- /dev/null
+++ b/tests/manual/examples/opengl/computegles31/glwindow.h
@@ -0,0 +1,63 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef GLWIDGET_H
+#define GLWIDGET_H
+
+#include <QOpenGLWindow>
+#include <QOpenGLTexture>
+#include <QMatrix4x4>
+#include <QVector3D>
+#include <QKeyEvent>
+#include <QPropertyAnimation>
+#include <QSequentialAnimationGroup>
+#include <QRectF>
+
+QT_BEGIN_NAMESPACE
+
+class QOpenGLTexture;
+class QOpenGLShaderProgram;
+class QOpenGLBuffer;
+class QOpenGLVertexArrayObject;
+
+QT_END_NAMESPACE
+
+class GLWindow : public QOpenGLWindow
+{
+ Q_OBJECT
+ Q_PROPERTY(float blurRadius READ blurRadius WRITE setBlurRadius)
+
+public:
+ GLWindow();
+ ~GLWindow();
+
+ void initializeGL() override;
+ void resizeGL(int w, int h) override;
+ void paintGL() override;
+
+ float blurRadius() const { return m_blurRadius; }
+ void setBlurRadius(float blurRadius);
+
+protected:
+ void keyPressEvent(QKeyEvent *e) override;
+ void setAnimating(bool animate);
+
+private:
+ QPropertyAnimation *m_animationForward = nullptr;
+ QPropertyAnimation *m_animationBackward = nullptr;
+ QSequentialAnimationGroup *m_animationGroup;
+ QOpenGLTexture *m_texImageInput = nullptr;
+ QOpenGLTexture *m_texImageTmp = nullptr;
+ QOpenGLTexture *m_texImageProcessed = nullptr;
+ QOpenGLShaderProgram *m_shaderDisplay = nullptr;
+ QOpenGLShaderProgram *m_shaderComputeV = nullptr;
+ QOpenGLShaderProgram *m_shaderComputeH = nullptr;
+ QMatrix4x4 m_proj;
+ QSizeF m_quadSize;
+
+ int m_blurRadius = 0;
+ bool m_animate = true;
+ QOpenGLVertexArrayObject *m_vao = nullptr;
+};
+
+#endif
diff --git a/tests/manual/examples/opengl/computegles31/main.cpp b/tests/manual/examples/opengl/computegles31/main.cpp
new file mode 100644
index 0000000000..6c29dd4de9
--- /dev/null
+++ b/tests/manual/examples/opengl/computegles31/main.cpp
@@ -0,0 +1,101 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QGuiApplication>
+#include <QSurfaceFormat>
+#include <QOffscreenSurface>
+#include <QOpenGLContext>
+#include <QDebug>
+#include <QPair>
+#include "glwindow.h"
+
+bool OGLSupports(int major, int minor, bool gles = false, QSurfaceFormat::OpenGLContextProfile profile = QSurfaceFormat::NoProfile)
+{
+ QOpenGLContext ctx;
+ QSurfaceFormat fmt;
+ fmt.setVersion(major, minor);
+ if (gles) {
+ fmt.setRenderableType(QSurfaceFormat::OpenGLES);
+ } else {
+ fmt.setRenderableType(QSurfaceFormat::OpenGL);
+ fmt.setProfile(profile);
+ }
+
+ ctx.setFormat(fmt);
+ ctx.create();
+ if (!ctx.isValid())
+ return false;
+ int ctxMajor = ctx.format().majorVersion();
+ int ctxMinor = ctx.format().minorVersion();
+ bool isGles = (ctx.format().renderableType() == QSurfaceFormat::OpenGLES);
+
+ if (isGles != gles) return false;
+ if (ctxMajor < major) return false;
+ if (ctxMajor == major && ctxMinor < minor)
+ return false;
+ if (!gles && ctx.format().profile() != profile)
+ return false;
+ return true;
+}
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+ qDebug() << "Support for GL 2.0 noprof "<<( OGLSupports(2,0,false) ? "yes" : "no");
+ qDebug() << "Support for GL 2.0 core "<<( OGLSupports(2,0,false, QSurfaceFormat::CoreProfile) ? "yes" : "no");
+ qDebug() << "Support for GL 2.0 compat "<<( OGLSupports(2,0,false, QSurfaceFormat::CompatibilityProfile) ? "yes" : "no");
+ qDebug() << "Support for GL 2.1 noprof "<<( OGLSupports(2,1,false) ? "yes" : "no");
+ qDebug() << "Support for GL 2.1 core "<<( OGLSupports(2,1,false, QSurfaceFormat::CoreProfile) ? "yes" : "no");
+ qDebug() << "Support for GL 2.1 compat "<<( OGLSupports(2,1,false, QSurfaceFormat::CompatibilityProfile) ? "yes" : "no");
+ qDebug() << "Support for GL 3.0 noprof "<<( OGLSupports(3,0,false) ? "yes" : "no");
+ qDebug() << "Support for GL 3.0 core "<<( OGLSupports(3,0,false, QSurfaceFormat::CoreProfile) ? "yes" : "no");
+ qDebug() << "Support for GL 3.0 compat "<<( OGLSupports(3,0,false, QSurfaceFormat::CompatibilityProfile) ? "yes" : "no");
+ qDebug() << "Support for GL 3.1 noprof "<<( OGLSupports(3,1,false) ? "yes" : "no");
+ qDebug() << "Support for GL 3.1 core "<<( OGLSupports(3,1,false, QSurfaceFormat::CoreProfile) ? "yes" : "no");
+ qDebug() << "Support for GL 3.1 compat "<<( OGLSupports(3,1,false, QSurfaceFormat::CompatibilityProfile) ? "yes" : "no");
+ qDebug() << "Support for GL 3.2 core "<<( OGLSupports(3,2,false,QSurfaceFormat::CoreProfile) ? "yes" : "no");
+ qDebug() << "Support for GL 3.2 compat "<<( OGLSupports(3,2,false,QSurfaceFormat::CompatibilityProfile) ? "yes" : "no");
+ qDebug() << "Support for GL 3.3 core "<<( OGLSupports(3,3,false,QSurfaceFormat::CoreProfile) ? "yes" : "no");
+ qDebug() << "Support for GL 3.3 compat "<<( OGLSupports(3,3,false,QSurfaceFormat::CompatibilityProfile) ? "yes" : "no");
+ qDebug() << "Support for GL 4.0 core "<<( OGLSupports(4,0,false,QSurfaceFormat::CoreProfile) ? "yes" : "no");
+ qDebug() << "Support for GL 4.0 compat "<<( OGLSupports(4,0,false,QSurfaceFormat::CompatibilityProfile) ? "yes" : "no");
+ qDebug() << "Support for GL 4.1 core "<<( OGLSupports(4,1,false,QSurfaceFormat::CoreProfile) ? "yes" : "no");
+ qDebug() << "Support for GL 4.1 compat "<<( OGLSupports(4,1,false,QSurfaceFormat::CompatibilityProfile) ? "yes" : "no");
+ qDebug() << "Support for GL 4.2 core "<<( OGLSupports(4,2,false,QSurfaceFormat::CoreProfile) ? "yes" : "no");
+ qDebug() << "Support for GL 4.2 compat "<<( OGLSupports(4,2,false,QSurfaceFormat::CompatibilityProfile) ? "yes" : "no");
+ qDebug() << "Support for GL 4.3 core "<<( OGLSupports(4,3,false,QSurfaceFormat::CoreProfile) ? "yes" : "no");
+ qDebug() << "Support for GL 4.3 compat "<<( OGLSupports(4,3,false,QSurfaceFormat::CompatibilityProfile) ? "yes" : "no");
+ qDebug() << "Support for GL 4.4 core "<<( OGLSupports(4,4,false,QSurfaceFormat::CoreProfile) ? "yes" : "no");
+ qDebug() << "Support for GL 4.4 compat "<<( OGLSupports(4,4,false,QSurfaceFormat::CompatibilityProfile) ? "yes" : "no");
+ qDebug() << "Support for GL 4.5 core "<<( OGLSupports(4,5,false,QSurfaceFormat::CoreProfile) ? "yes" : "no");
+ qDebug() << "Support for GL 4.5 compat "<<( OGLSupports(4,5,false,QSurfaceFormat::CompatibilityProfile) ? "yes" : "no");
+ qDebug() << "Support for GLES 2.0 "<<( OGLSupports(2,0,true) ? "yes" : "no");
+ qDebug() << "Support for GLES 3.0 "<<( OGLSupports(3,0,true) ? "yes" : "no");
+ qDebug() << "Support for GLES 3.1 "<<( OGLSupports(3,1,true) ? "yes" : "no");
+ qDebug() << "Support for GLES 3.2 "<<( OGLSupports(3,2,true) ? "yes" : "no");
+
+ QSurfaceFormat fmt;
+ fmt.setDepthBufferSize(24);
+
+ // Request OpenGL ES 3.1 context, as this is a GLES example. If not available, go for OpenGL 4.3 core.
+ if (OGLSupports(3,1,true)) {
+ qDebug("Requesting 3.1 GLES context");
+ fmt.setVersion(3, 1);
+ fmt.setRenderableType(QSurfaceFormat::OpenGLES);
+ } else if (OGLSupports(4,3,false,QSurfaceFormat::CoreProfile)) {
+ qDebug("Requesting 4.3 core context");
+ fmt.setVersion(4, 3);
+ fmt.setRenderableType(QSurfaceFormat::OpenGL);
+ fmt.setProfile(QSurfaceFormat::CoreProfile);
+ } else {
+ qWarning("Error: This system does not support OpenGL Compute Shaders! Exiting.");
+ return -1;
+ }
+ QSurfaceFormat::setDefaultFormat(fmt);
+
+ GLWindow glWindow;
+ glWindow.showMaximized();
+
+ return app.exec();
+}
diff --git a/tests/manual/examples/opengl/contextinfo/CMakeLists.txt b/tests/manual/examples/opengl/contextinfo/CMakeLists.txt
new file mode 100644
index 0000000000..ae5c10942a
--- /dev/null
+++ b/tests/manual/examples/opengl/contextinfo/CMakeLists.txt
@@ -0,0 +1,39 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(contextinfo LANGUAGES CXX)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/opengl/contextinfo")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui OpenGL Widgets)
+
+qt_standard_project_setup()
+
+qt_add_executable(contextinfo
+ main.cpp
+ renderwindow.cpp renderwindow.h
+ widget.cpp widget.h
+)
+
+set_target_properties(contextinfo PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(contextinfo PRIVATE
+ Qt6::Core
+ Qt6::Gui
+ Qt6::OpenGL
+ Qt6::Widgets
+)
+
+install(TARGETS contextinfo
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/tests/manual/examples/opengl/contextinfo/contextinfo.pro b/tests/manual/examples/opengl/contextinfo/contextinfo.pro
new file mode 100644
index 0000000000..ae8f4067b6
--- /dev/null
+++ b/tests/manual/examples/opengl/contextinfo/contextinfo.pro
@@ -0,0 +1,14 @@
+TEMPLATE = app
+QT += widgets opengl
+requires(qtConfig(filedialog))
+
+SOURCES += main.cpp \
+ widget.cpp \
+ renderwindow.cpp
+
+HEADERS += widget.h \
+ renderwindow.h
+
+# install
+target.path = $$[QT_INSTALL_EXAMPLES]/opengl/contextinfo
+INSTALLS += target
diff --git a/tests/manual/examples/opengl/contextinfo/main.cpp b/tests/manual/examples/opengl/contextinfo/main.cpp
new file mode 100644
index 0000000000..253425ded9
--- /dev/null
+++ b/tests/manual/examples/opengl/contextinfo/main.cpp
@@ -0,0 +1,25 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QApplication>
+#include "widget.h"
+
+int main(int argc, char **argv)
+{
+ for (int i = 1; i < argc; ++i) {
+ if (!qstrcmp(argv[i], "-g"))
+ QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);
+ else if (!qstrcmp(argv[i], "-s"))
+ QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);
+ else if (!qstrcmp(argv[i], "-d"))
+ QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
+ }
+
+ QApplication app(argc, argv);
+
+ Widget w;
+ w.resize(700, 800);
+ w.show();
+
+ return app.exec();
+}
diff --git a/tests/manual/examples/opengl/contextinfo/renderwindow.cpp b/tests/manual/examples/opengl/contextinfo/renderwindow.cpp
new file mode 100644
index 0000000000..64c0f47db9
--- /dev/null
+++ b/tests/manual/examples/opengl/contextinfo/renderwindow.cpp
@@ -0,0 +1,191 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "renderwindow.h"
+#include <QTimer>
+#include <QMatrix4x4>
+#include <QOpenGLContext>
+#include <QtOpenGL/QOpenGLShaderProgram>
+#include <QOpenGLFunctions>
+
+RenderWindow::RenderWindow(const QSurfaceFormat &format)
+ : m_context(nullptr),
+ m_initialized(false),
+ m_forceGLSL110(false),
+ m_angle(0.0f)
+{
+ setSurfaceType(QWindow::OpenGLSurface);
+ setFormat(format);
+ m_context = new QOpenGLContext(this);
+ m_context->setFormat(requestedFormat());
+ if (!m_context->create()) {
+ delete m_context;
+ m_context = nullptr;
+ }
+}
+
+void RenderWindow::exposeEvent(QExposeEvent *)
+{
+ if (isExposed())
+ render();
+}
+
+// ES needs the precision qualifiers.
+// On desktop GL QOpenGLShaderProgram inserts dummy defines for highp/mediump/lowp.
+static const char *vertexShaderSource110 =
+ "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 *fragmentShaderSource110 =
+ "varying lowp vec4 col;\n"
+ "void main() {\n"
+ " gl_FragColor = col;\n"
+ "}\n";
+
+static const char *vertexShaderSource =
+ "#version 150\n"
+ "in vec4 posAttr;\n"
+ "in vec4 colAttr;\n"
+ "out vec4 col;\n"
+ "uniform mat4 matrix;\n"
+ "void main() {\n"
+ " col = colAttr;\n"
+ " gl_Position = matrix * posAttr;\n"
+ "}\n";
+
+static const char *fragmentShaderSource =
+ "#version 150\n"
+ "in vec4 col;\n"
+ "out vec4 fragColor;\n"
+ "void main() {\n"
+ " fragColor = col;\n"
+ "}\n";
+
+static GLfloat vertices[] = {
+ 0.0f, 0.707f,
+ -0.5f, -0.5f,
+ 0.5f, -0.5f
+};
+
+static GLfloat colors[] = {
+ 1.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f
+};
+
+void RenderWindow::init()
+{
+ m_program = new QOpenGLShaderProgram(this);
+
+ QSurfaceFormat format = m_context->format();
+ bool useNewStyleShader = format.profile() == QSurfaceFormat::CoreProfile;
+ // Try to handle 3.0 & 3.1 that do not have the core/compatibility profile concept 3.2+ has.
+ // This may still fail since version 150 (3.2) is specified in the sources but it's worth a try.
+ if (format.renderableType() == QSurfaceFormat::OpenGL && format.majorVersion() == 3 && format.minorVersion() <= 1)
+ useNewStyleShader = !format.testOption(QSurfaceFormat::DeprecatedFunctions);
+ if (m_forceGLSL110)
+ useNewStyleShader = false;
+
+ const char *vsrc = useNewStyleShader ? vertexShaderSource : vertexShaderSource110;
+ const char *fsrc = useNewStyleShader ? fragmentShaderSource : fragmentShaderSource110;
+ qDebug("Using version %s shader", useNewStyleShader ? "150" : "110");
+
+ if (!m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vsrc)) {
+ emit error(m_program->log());
+ return;
+ }
+ if (!m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fsrc)) {
+ emit error(m_program->log());
+ return;
+ }
+ if (!m_program->link()) {
+ emit error(m_program->log());
+ return;
+ }
+
+ m_posAttr = m_program->attributeLocation("posAttr");
+ m_colAttr = m_program->attributeLocation("colAttr");
+ m_matrixUniform = m_program->uniformLocation("matrix");
+
+ m_vbo.create();
+ m_vbo.bind();
+ m_vbo.allocate(vertices, sizeof(vertices) + sizeof(colors));
+ m_vbo.write(sizeof(vertices), colors, sizeof(colors));
+ m_vbo.release();
+
+ QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao);
+ if (m_vao.isCreated()) // have VAO support, use it
+ setupVertexAttribs();
+}
+
+void RenderWindow::setupVertexAttribs()
+{
+ m_vbo.bind();
+ m_program->setAttributeBuffer(m_posAttr, GL_FLOAT, 0, 2);
+ m_program->setAttributeBuffer(m_colAttr, GL_FLOAT, sizeof(vertices), 3);
+ m_program->enableAttributeArray(m_posAttr);
+ m_program->enableAttributeArray(m_colAttr);
+ m_vbo.release();
+}
+
+bool RenderWindow::event(QEvent *ev)
+{
+ if (ev->type() == QEvent::UpdateRequest)
+ render();
+ return QWindow::event(ev);
+}
+
+void RenderWindow::render()
+{
+ if (!m_context->makeCurrent(this)) {
+ emit error(tr("makeCurrent() failed"));
+ return;
+ }
+
+ QOpenGLFunctions *f = m_context->functions();
+ if (!m_initialized) {
+ m_initialized = true;
+ f->glEnable(GL_DEPTH_TEST);
+ f->glClearColor(0, 0, 0, 1);
+ init();
+ emit ready();
+ }
+
+ if (!m_vbo.isCreated()) // init() failed, don't bother with trying to render
+ return;
+
+ const qreal retinaScale = devicePixelRatio();
+ f->glViewport(0, 0, width() * retinaScale, height() * retinaScale);
+ f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ m_program->bind();
+ QMatrix4x4 matrix;
+ matrix.perspective(60.0f, 4.0f / 3.0f, 0.1f, 100.0f);
+ matrix.translate(0.0f, 0.0f, -2.0f);
+ matrix.rotate(m_angle, 0.0f, 1.0f, 0.0f);
+ m_program->setUniformValue(m_matrixUniform, matrix);
+
+ if (m_vao.isCreated())
+ m_vao.bind();
+ else // no VAO support, set the vertex attribute arrays now
+ setupVertexAttribs();
+
+ f->glDrawArrays(GL_TRIANGLES, 0, 3);
+
+ m_vao.release();
+ m_program->release();
+
+ // swapInterval is 1 by default which means that swapBuffers() will (hopefully) block
+ // and wait for vsync.
+ m_context->swapBuffers(this);
+
+ m_angle += 1.0f;
+
+ requestUpdate();
+}
diff --git a/tests/manual/examples/opengl/contextinfo/renderwindow.h b/tests/manual/examples/opengl/contextinfo/renderwindow.h
new file mode 100644
index 0000000000..964dd601a6
--- /dev/null
+++ b/tests/manual/examples/opengl/contextinfo/renderwindow.h
@@ -0,0 +1,48 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef RENDERWINDOW_H
+#define RENDERWINDOW_H
+
+#include <QWindow>
+#include <QOpenGLVertexArrayObject>
+#include <QOpenGLBuffer>
+
+QT_FORWARD_DECLARE_CLASS(QOpenGLContext)
+QT_FORWARD_DECLARE_CLASS(QOpenGLShaderProgram)
+
+class RenderWindow : public QWindow
+{
+ Q_OBJECT
+
+public:
+ RenderWindow(const QSurfaceFormat &format);
+ QOpenGLContext *context() { return m_context; }
+ void exposeEvent(QExposeEvent *) override;
+ void setForceGLSL110(bool enable) { m_forceGLSL110 = enable; }
+
+signals:
+ void ready();
+ void error(const QString &msg);
+
+protected:
+ bool event(QEvent *ev) override;
+
+private slots:
+ void render();
+
+private:
+ void init();
+ void setupVertexAttribs();
+
+ QOpenGLContext *m_context;
+ bool m_initialized;
+ bool m_forceGLSL110;
+ QOpenGLShaderProgram *m_program;
+ int m_posAttr, m_colAttr, m_matrixUniform;
+ QOpenGLVertexArrayObject m_vao;
+ QOpenGLBuffer m_vbo;
+ float m_angle;
+};
+
+#endif // RENDERWINDOW_H
diff --git a/tests/manual/examples/opengl/contextinfo/widget.cpp b/tests/manual/examples/opengl/contextinfo/widget.cpp
new file mode 100644
index 0000000000..bc31497039
--- /dev/null
+++ b/tests/manual/examples/opengl/contextinfo/widget.cpp
@@ -0,0 +1,360 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "widget.h"
+#include "renderwindow.h"
+#include <QVBoxLayout>
+#include <QComboBox>
+#include <QGroupBox>
+#include <QRadioButton>
+#include <QCheckBox>
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QList>
+#include <QByteArray>
+#include <QPushButton>
+#include <QTextEdit>
+#include <QSplitter>
+#include <QGuiApplication>
+#include <QSurfaceFormat>
+#include <QOpenGLContext>
+#include <QOpenGLFunctions>
+#include <QDebug>
+#include <QTextStream>
+
+struct Version {
+ const char *str;
+ int major;
+ int minor;
+};
+
+static struct Version versions[] = {
+ { "1.0", 1, 0 },
+ { "1.1", 1, 1 },
+ { "1.2", 1, 2 },
+ { "1.3", 1, 3 },
+ { "1.4", 1, 4 },
+ { "1.5", 1, 5 },
+ { "2.0", 2, 0 },
+ { "2.1", 2, 1 },
+ { "3.0", 3, 0 },
+ { "3.1", 3, 1 },
+ { "3.2", 3, 2 },
+ { "3.3", 3, 3 },
+ { "4.0", 4, 0 },
+ { "4.1", 4, 1 },
+ { "4.2", 4, 2 },
+ { "4.3", 4, 3 },
+ { "4.4", 4, 4 },
+ { "4.5", 4, 5 }
+};
+
+struct Profile {
+ const char *str;
+ QSurfaceFormat::OpenGLContextProfile profile;
+};
+
+static struct Profile profiles[] = {
+ { "none", QSurfaceFormat::NoProfile },
+ { "core", QSurfaceFormat::CoreProfile },
+ { "compatibility", QSurfaceFormat::CompatibilityProfile }
+};
+
+struct Option {
+ const char *str;
+ QSurfaceFormat::FormatOption option;
+};
+
+static struct Option options[] = {
+ { "deprecated functions (not forward compatible)", QSurfaceFormat::DeprecatedFunctions },
+ { "debug context", QSurfaceFormat::DebugContext },
+ { "stereo buffers", QSurfaceFormat::StereoBuffers },
+ // This is not a QSurfaceFormat option but is helpful to determine if the driver
+ // allows compiling old-style shaders with core profile.
+ { "force version 110 shaders", QSurfaceFormat::FormatOption(0) }
+};
+
+struct Renderable {
+ const char *str;
+ QSurfaceFormat::RenderableType renderable;
+};
+
+static struct Renderable renderables[] = {
+ { "default", QSurfaceFormat::DefaultRenderableType },
+#ifndef Q_OS_ANDROID
+ { "OpenGL", QSurfaceFormat::OpenGL },
+#endif
+ { "OpenGL ES", QSurfaceFormat::OpenGLES }
+};
+
+void Widget::addVersions(QLayout *layout)
+{
+ QHBoxLayout *hbox = new QHBoxLayout;
+ hbox->setSpacing(20);
+ QLabel *label = new QLabel(tr("Context &version: "));
+ hbox->addWidget(label);
+ m_version = new QComboBox;
+ m_version->setMinimumWidth(60);
+ label->setBuddy(m_version);
+ hbox->addWidget(m_version);
+ for (size_t i = 0; i < sizeof(versions) / sizeof(Version); ++i) {
+ m_version->addItem(QString::fromLatin1(versions[i].str));
+ if (versions[i].major == 2 && versions[i].minor == 0)
+ m_version->setCurrentIndex(m_version->count() - 1);
+ }
+
+ QPushButton *btn = new QPushButton(tr("Create context"));
+ connect(btn, &QPushButton::clicked, this, &Widget::start);
+ btn->setMinimumSize(120, 40);
+ hbox->addWidget(btn);
+
+ layout->addItem(hbox);
+}
+
+void Widget::addProfiles(QLayout *layout)
+{
+ QGroupBox *groupBox = new QGroupBox(tr("Profile"));
+ QVBoxLayout *vbox = new QVBoxLayout;
+ for (size_t i = 0; i < sizeof(profiles) / sizeof(Profile); ++i)
+ vbox->addWidget(new QRadioButton(QString::fromLatin1(profiles[i].str)));
+ static_cast<QRadioButton *>(vbox->itemAt(0)->widget())->setChecked(true);
+ groupBox->setLayout(vbox);
+ layout->addWidget(groupBox);
+ m_profiles = vbox;
+}
+
+void Widget::addOptions(QLayout *layout)
+{
+ QGroupBox *groupBox = new QGroupBox(tr("Options"));
+ QVBoxLayout *vbox = new QVBoxLayout;
+ for (size_t i = 0; i < sizeof(options) / sizeof(Option); ++i)
+ vbox->addWidget(new QCheckBox(QString::fromLatin1(options[i].str)));
+ groupBox->setLayout(vbox);
+ layout->addWidget(groupBox);
+ m_options = vbox;
+}
+
+void Widget::addRenderableTypes(QLayout *layout)
+{
+ QGroupBox *groupBox = new QGroupBox(tr("Renderable type"));
+ QVBoxLayout *vbox = new QVBoxLayout;
+ for (size_t i = 0; i < sizeof(renderables) / sizeof(Renderable); ++i)
+ vbox->addWidget(new QRadioButton(QString::fromLatin1(renderables[i].str)));
+ static_cast<QRadioButton *>(vbox->itemAt(0)->widget())->setChecked(true);
+ groupBox->setLayout(vbox);
+ layout->addWidget(groupBox);
+ m_renderables = vbox;
+}
+
+void Widget::addRenderWindow()
+{
+ m_renderWindowLayout->addWidget(m_renderWindowContainer);
+}
+
+static QWidget *widgetWithLayout(QLayout *layout)
+{
+ QWidget *w = new QWidget;
+ w->setLayout(layout);
+ return w;
+}
+
+Widget::Widget(QWidget *parent)
+ : QWidget(parent)
+{
+ QVBoxLayout *layout = new QVBoxLayout;
+ QSplitter *vsplit = new QSplitter(Qt::Vertical);
+ layout->addWidget(vsplit);
+
+ QSplitter *hsplit = new QSplitter;
+
+ QVBoxLayout *settingsLayout = new QVBoxLayout;
+ addVersions(settingsLayout);
+ addProfiles(settingsLayout);
+ addOptions(settingsLayout);
+ addRenderableTypes(settingsLayout);
+ hsplit->addWidget(widgetWithLayout(settingsLayout));
+
+ QVBoxLayout *outputLayout = new QVBoxLayout;
+ m_output = new QTextEdit;
+ m_output->setReadOnly(true);
+ outputLayout->addWidget(m_output);
+ m_extensions = new QTextEdit;
+ m_extensions->setReadOnly(true);
+ outputLayout->addWidget(m_extensions);
+ hsplit->addWidget(widgetWithLayout(outputLayout));
+
+ hsplit->setStretchFactor(0, 4);
+ hsplit->setStretchFactor(1, 6);
+ vsplit->addWidget(hsplit);
+
+ m_renderWindowLayout = new QVBoxLayout;
+ vsplit->addWidget(widgetWithLayout(m_renderWindowLayout));
+ vsplit->setStretchFactor(1, 5);
+
+ m_renderWindowContainer = new QWidget;
+ addRenderWindow();
+
+ QString description;
+ QTextStream str(&description);
+ str << "Qt " << QT_VERSION_STR << ' ' << QGuiApplication::platformName();
+ const char *openGlVariables[] =
+ {"QT_ANGLE_PLATFORM", "QT_OPENGL", "QT_OPENGL_BUGLIST", "QT_OPENGL_DLL"};
+ const size_t variableCount = sizeof(openGlVariables) / sizeof(openGlVariables[0]);
+ for (size_t v = 0; v < variableCount; ++v) {
+ if (qEnvironmentVariableIsSet(openGlVariables[v]))
+ str << ' ' << openGlVariables[v] << '=' << qgetenv(openGlVariables[v]);
+ }
+ if (QCoreApplication::testAttribute(Qt::AA_UseOpenGLES))
+ str << " Qt::AA_UseOpenGLES";
+ if (QCoreApplication::testAttribute(Qt::AA_UseSoftwareOpenGL))
+ str << " Qt::AA_UseSoftwareOpenGL";
+ if (QCoreApplication::testAttribute(Qt::AA_UseDesktopOpenGL))
+ str << " Qt::AA_UseDesktopOpenGL";
+ layout->addWidget(new QLabel(description));
+
+ setLayout(layout);
+}
+
+void Widget::start()
+{
+ QSurfaceFormat fmt;
+
+ int idx = m_version->currentIndex();
+ if (idx < 0)
+ return;
+ fmt.setVersion(versions[idx].major, versions[idx].minor);
+
+ for (size_t i = 0; i < sizeof(profiles) / sizeof(Profile); ++i)
+ if (static_cast<QRadioButton *>(m_profiles->itemAt(int(i))->widget())->isChecked()) {
+ fmt.setProfile(profiles[i].profile);
+ break;
+ }
+
+ bool forceGLSL110 = false;
+ for (size_t i = 0; i < sizeof(options) / sizeof(Option); ++i)
+ if (static_cast<QCheckBox *>(m_options->itemAt(int(i))->widget())->isChecked()) {
+ if (options[i].option)
+ fmt.setOption(options[i].option);
+ else if (i == 3)
+ forceGLSL110 = true;
+ }
+
+ for (size_t i = 0; i < sizeof(renderables) / sizeof(Renderable); ++i)
+ if (static_cast<QRadioButton *>(m_renderables->itemAt(int(i))->widget())->isChecked()) {
+ fmt.setRenderableType(renderables[i].renderable);
+ break;
+ }
+
+ // The example rendering will need a depth buffer.
+ fmt.setDepthBufferSize(16);
+
+ m_output->clear();
+ m_extensions->clear();
+ qDebug() << "Requesting surface format" << fmt;
+
+ m_renderWindowLayout->removeWidget(m_renderWindowContainer);
+ delete m_renderWindowContainer;
+
+ RenderWindow *renderWindow = new RenderWindow(fmt);
+ if (!renderWindow->context()) {
+ m_output->append(tr("Failed to create context"));
+ delete renderWindow;
+ m_renderWindowContainer = new QWidget;
+ addRenderWindow();
+ return;
+ }
+ m_surface = renderWindow;
+
+ renderWindow->setForceGLSL110(forceGLSL110);
+ connect(renderWindow, &RenderWindow::ready, this, &Widget::renderWindowReady);
+ connect(renderWindow, &RenderWindow::error, this, &Widget::renderWindowError);
+
+ m_renderWindowContainer = QWidget::createWindowContainer(renderWindow);
+ addRenderWindow();
+}
+
+void Widget::printFormat(const QSurfaceFormat &format)
+{
+ m_output->append(tr("OpenGL version: %1.%2").arg(format.majorVersion()).arg(format.minorVersion()));
+
+ for (size_t i = 0; i < sizeof(profiles) / sizeof(Profile); ++i)
+ if (profiles[i].profile == format.profile()) {
+ m_output->append(tr("Profile: %1").arg(QString::fromLatin1(profiles[i].str)));
+ break;
+ }
+
+ QString opts;
+ for (size_t i = 0; i < sizeof(options) / sizeof(Option); ++i)
+ if (format.testOption(options[i].option))
+ opts += QString::fromLatin1(options[i].str) + QLatin1Char(' ');
+ m_output->append(tr("Options: %1").arg(opts));
+
+ for (size_t i = 0; i < sizeof(renderables) / sizeof(Renderable); ++i)
+ if (renderables[i].renderable == format.renderableType()) {
+ m_output->append(tr("Renderable type: %1").arg(QString::fromLatin1(renderables[i].str)));
+ break;
+ }
+
+ m_output->append(tr("Depth buffer size: %1").arg(QString::number(format.depthBufferSize())));
+ m_output->append(tr("Stencil buffer size: %1").arg(QString::number(format.stencilBufferSize())));
+ m_output->append(tr("Samples: %1").arg(QString::number(format.samples())));
+ m_output->append(tr("Red buffer size: %1").arg(QString::number(format.redBufferSize())));
+ m_output->append(tr("Green buffer size: %1").arg(QString::number(format.greenBufferSize())));
+ m_output->append(tr("Blue buffer size: %1").arg(QString::number(format.blueBufferSize())));
+ m_output->append(tr("Alpha buffer size: %1").arg(QString::number(format.alphaBufferSize())));
+ m_output->append(tr("Swap interval: %1").arg(QString::number(format.swapInterval())));
+}
+
+void Widget::renderWindowReady()
+{
+ QOpenGLContext *context = QOpenGLContext::currentContext();
+ Q_ASSERT(context);
+
+ QString vendor, renderer, version, glslVersion;
+ const GLubyte *p;
+ QOpenGLFunctions *f = context->functions();
+ if ((p = f->glGetString(GL_VENDOR)))
+ vendor = QString::fromLatin1(reinterpret_cast<const char *>(p));
+ if ((p = f->glGetString(GL_RENDERER)))
+ renderer = QString::fromLatin1(reinterpret_cast<const char *>(p));
+ if ((p = f->glGetString(GL_VERSION)))
+ version = QString::fromLatin1(reinterpret_cast<const char *>(p));
+ if ((p = f->glGetString(GL_SHADING_LANGUAGE_VERSION)))
+ glslVersion = QString::fromLatin1(reinterpret_cast<const char *>(p));
+
+ m_output->append(tr("*** Context information ***"));
+ m_output->append(tr("Vendor: %1").arg(vendor));
+ m_output->append(tr("Renderer: %1").arg(renderer));
+ m_output->append(tr("OpenGL version: %1").arg(version));
+ m_output->append(tr("GLSL version: %1").arg(glslVersion));
+
+ m_output->append(tr("\n*** QSurfaceFormat from context ***"));
+ printFormat(context->format());
+
+ m_output->append(tr("\n*** QSurfaceFormat from window surface ***"));
+ printFormat(m_surface->format());
+
+ m_output->append(tr("\n*** Qt build information ***"));
+ const char *gltype[] = { "Desktop", "GLES 2", "GLES 1" };
+ m_output->append(tr("Qt OpenGL configuration: %1")
+ .arg(QString::fromLatin1(gltype[QOpenGLContext::openGLModuleType()])));
+#if defined(Q_OS_WIN)
+ using namespace QNativeInterface;
+ m_output->append(tr("Qt OpenGL library handle: %1")
+ .arg(QString::number(qintptr(QWGLContext::openGLModuleHandle()), 16)));
+#endif
+
+ QList<QByteArray> extensionList = context->extensions().values();
+ std::sort(extensionList.begin(), extensionList.end());
+ m_extensions->append(tr("Found %1 extensions:").arg(extensionList.count()));
+ for (const QByteArray &ext : std::as_const(extensionList))
+ m_extensions->append(QString::fromLatin1(ext));
+
+ m_output->moveCursor(QTextCursor::Start);
+ m_extensions->moveCursor(QTextCursor::Start);
+}
+
+void Widget::renderWindowError(const QString &msg)
+{
+ m_output->append(tr("An error has occurred:\n%1").arg(msg));
+}
diff --git a/tests/manual/examples/opengl/contextinfo/widget.h b/tests/manual/examples/opengl/contextinfo/widget.h
new file mode 100644
index 0000000000..e5c31fcaa7
--- /dev/null
+++ b/tests/manual/examples/opengl/contextinfo/widget.h
@@ -0,0 +1,46 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef WIDGET_H
+#define WIDGET_H
+
+#include <QWidget>
+
+QT_FORWARD_DECLARE_CLASS(QComboBox)
+QT_FORWARD_DECLARE_CLASS(QTextEdit)
+QT_FORWARD_DECLARE_CLASS(QVBoxLayout)
+QT_FORWARD_DECLARE_CLASS(QSurfaceFormat)
+QT_FORWARD_DECLARE_CLASS(QSurface)
+
+class Widget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit Widget(QWidget *parent = nullptr);
+
+private slots:
+ void start();
+ void renderWindowReady();
+ void renderWindowError(const QString &msg);
+
+private:
+ void addVersions(QLayout *layout);
+ void addProfiles(QLayout *layout);
+ void addOptions(QLayout *layout);
+ void addRenderableTypes(QLayout *layout);
+ void addRenderWindow();
+ void printFormat(const QSurfaceFormat &format);
+
+ QComboBox *m_version;
+ QLayout *m_profiles;
+ QLayout *m_options;
+ QLayout *m_renderables;
+ QTextEdit *m_output;
+ QTextEdit *m_extensions;
+ QVBoxLayout *m_renderWindowLayout;
+ QWidget *m_renderWindowContainer;
+ QSurface *m_surface;
+};
+
+#endif // WIDGET_H
diff --git a/tests/manual/examples/opengl/hellowindow/CMakeLists.txt b/tests/manual/examples/opengl/hellowindow/CMakeLists.txt
new file mode 100644
index 0000000000..3224c81ccc
--- /dev/null
+++ b/tests/manual/examples/opengl/hellowindow/CMakeLists.txt
@@ -0,0 +1,39 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(hellowindow LANGUAGES CXX)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/opengl/hellowindow")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui OpenGL)
+
+qt_standard_project_setup()
+
+qt_add_executable(hellowindow
+ hellowindow.cpp hellowindow.h
+ main.cpp
+)
+
+set_target_properties(hellowindow PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(hellowindow PRIVATE
+ Qt6::Core
+ Qt6::CorePrivate
+ Qt6::Gui
+ Qt6::GuiPrivate
+ Qt6::OpenGL
+)
+
+install(TARGETS hellowindow
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/tests/manual/examples/opengl/hellowindow/hellowindow.cpp b/tests/manual/examples/opengl/hellowindow/hellowindow.cpp
new file mode 100644
index 0000000000..c9cad95563
--- /dev/null
+++ b/tests/manual/examples/opengl/hellowindow/hellowindow.cpp
@@ -0,0 +1,305 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "hellowindow.h"
+
+#include <QOpenGLContext>
+#include <QOpenGLFunctions>
+#include <QRandomGenerator>
+#include <qmath.h>
+#include <QElapsedTimer>
+
+Renderer::Renderer(const QSurfaceFormat &format, Renderer *share, QScreen *screen)
+ : m_initialized(false)
+ , m_format(format)
+{
+ m_context = new QOpenGLContext(this);
+ if (screen)
+ m_context->setScreen(screen);
+ m_context->setFormat(format);
+ if (share)
+ m_context->setShareContext(share->m_context);
+ m_context->create();
+
+ m_backgroundColor = QColor::fromRgbF(0.1f, 0.1f, 0.2f, 1.0f);
+ m_backgroundColor.setRed(QRandomGenerator::global()->bounded(64));
+ m_backgroundColor.setGreen(QRandomGenerator::global()->bounded(128));
+ m_backgroundColor.setBlue(QRandomGenerator::global()->bounded(256));
+}
+
+HelloWindow::HelloWindow(const QSharedPointer<Renderer> &renderer, QScreen *screen)
+ : m_colorIndex(0), m_renderer(renderer)
+{
+ setSurfaceType(QWindow::OpenGLSurface);
+ setFlags(Qt::Window | Qt::WindowSystemMenuHint | Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint);
+
+ setGeometry(QRect(10, 10, 640, 480));
+
+ setFormat(renderer->format());
+ if (screen)
+ setScreen(screen);
+
+ create();
+
+ updateColor();
+
+ connect(renderer.data(), &Renderer::requestUpdate, this, &QWindow::requestUpdate);
+}
+
+void HelloWindow::exposeEvent(QExposeEvent *)
+{
+ if (isExposed())
+ render();
+}
+
+bool HelloWindow::event(QEvent *ev)
+{
+ if (ev->type() == QEvent::UpdateRequest && isExposed())
+ render();
+ return QWindow::event(ev);
+}
+
+void HelloWindow::render()
+{
+ static QElapsedTimer timer;
+ if (!timer.isValid())
+ timer.start();
+ qreal a = (qreal)(((timer.elapsed() * 3) % 36000) / 100.0);
+ auto call = [this, r = m_renderer.data(), a, c = color()]() { r->render(this, a, c); };
+ QMetaObject::invokeMethod(m_renderer.data(), call);
+}
+
+void HelloWindow::mousePressEvent(QMouseEvent *)
+{
+ updateColor();
+}
+
+QColor HelloWindow::color() const
+{
+ return m_color;
+}
+
+void HelloWindow::updateColor()
+{
+ QColor colors[] =
+ {
+ QColor(100, 255, 0),
+ QColor(0, 100, 255)
+ };
+
+ m_color = colors[m_colorIndex];
+ m_colorIndex = 1 - m_colorIndex;
+}
+
+void Renderer::render(HelloWindow *surface, qreal angle, const QColor &color)
+{
+ if (!m_context->makeCurrent(surface))
+ return;
+
+ QSize viewSize = surface->size();
+
+ if (!m_initialized) {
+ initialize();
+ m_initialized = true;
+ }
+
+ QOpenGLFunctions *f = m_context->functions();
+ f->glViewport(0, 0, viewSize.width() * surface->devicePixelRatio(), viewSize.height() * surface->devicePixelRatio());
+ f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ f->glClearColor(m_backgroundColor.redF(), m_backgroundColor.greenF(), m_backgroundColor.blueF(), m_backgroundColor.alphaF());
+ f->glFrontFace(GL_CW);
+ f->glCullFace(GL_FRONT);
+ f->glEnable(GL_CULL_FACE);
+ f->glEnable(GL_DEPTH_TEST);
+
+ m_program->bind();
+ m_vbo.bind();
+
+ m_program->enableAttributeArray(vertexAttr);
+ m_program->enableAttributeArray(normalAttr);
+ m_program->setAttributeBuffer(vertexAttr, GL_FLOAT, 0, 3);
+ const int verticesSize = vertices.count() * 3 * sizeof(GLfloat);
+ m_program->setAttributeBuffer(normalAttr, GL_FLOAT, verticesSize, 3);
+
+ QMatrix4x4 modelview;
+ modelview.rotate(angle, 0.0f, 1.0f, 0.0f);
+ modelview.rotate(angle, 1.0f, 0.0f, 0.0f);
+ modelview.rotate(angle, 0.0f, 0.0f, 1.0f);
+ modelview.translate(0.0f, -0.2f, 0.0f);
+
+ m_program->setUniformValue(matrixUniform, modelview);
+ m_program->setUniformValue(colorUniform, color);
+
+ m_context->functions()->glDrawArrays(GL_TRIANGLES, 0, vertices.size());
+
+ m_context->swapBuffers(surface);
+
+ emit requestUpdate();
+}
+
+Q_GLOBAL_STATIC(QMutex, initMutex)
+
+void Renderer::initialize()
+{
+ // Threaded shader compilation can confuse some drivers. Avoid it.
+ QMutexLocker lock(initMutex());
+
+ QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex, this);
+ vshader->compileSourceCode(
+ "attribute highp vec4 vertex;"
+ "attribute mediump vec3 normal;"
+ "uniform mediump mat4 matrix;"
+ "uniform lowp vec4 sourceColor;"
+ "varying mediump vec4 color;"
+ "void main(void)"
+ "{"
+ " vec3 toLight = normalize(vec3(0.0, 0.3, 1.0));"
+ " float angle = max(dot(normal, toLight), 0.0);"
+ " vec3 col = sourceColor.rgb;"
+ " color = vec4(col * 0.2 + col * 0.8 * angle, 1.0);"
+ " color = clamp(color, 0.0, 1.0);"
+ " gl_Position = matrix * vertex;"
+ "}");
+
+ QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment, this);
+ fshader->compileSourceCode(
+ "varying mediump vec4 color;"
+ "void main(void)"
+ "{"
+ " gl_FragColor = color;"
+ "}");
+
+ m_program = new QOpenGLShaderProgram(this);
+ m_program->addShader(vshader);
+ m_program->addShader(fshader);
+ m_program->link();
+ m_program->bind();
+
+ vertexAttr = m_program->attributeLocation("vertex");
+ normalAttr = m_program->attributeLocation("normal");
+ matrixUniform = m_program->uniformLocation("matrix");
+ colorUniform = m_program->uniformLocation("sourceColor");
+
+ createGeometry();
+
+ m_vbo.create();
+ m_vbo.bind();
+ const int verticesSize = vertices.count() * 3 * sizeof(GLfloat);
+ m_vbo.allocate(verticesSize * 2);
+ m_vbo.write(0, vertices.constData(), verticesSize);
+ m_vbo.write(verticesSize, normals.constData(), verticesSize);
+}
+
+void Renderer::createGeometry()
+{
+ vertices.clear();
+ normals.clear();
+
+ qreal x1 = +0.06f;
+ qreal y1 = -0.14f;
+ qreal x2 = +0.14f;
+ qreal y2 = -0.06f;
+ qreal x3 = +0.08f;
+ qreal y3 = +0.00f;
+ qreal x4 = +0.30f;
+ qreal 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;
+ const qreal sectorAngle = 2 * qreal(M_PI) / NumSectors;
+ for (int i = 0; i < NumSectors; ++i) {
+ qreal angle = i * sectorAngle;
+ qreal x5 = 0.30 * qSin(angle);
+ qreal y5 = 0.30 * qCos(angle);
+ qreal x6 = 0.20 * qSin(angle);
+ qreal y6 = 0.20 * qCos(angle);
+
+ angle += sectorAngle;
+ qreal x7 = 0.20 * qSin(angle);
+ qreal y7 = 0.20 * qCos(angle);
+ qreal x8 = 0.30 * qSin(angle);
+ qreal y8 = 0.30 * qCos(angle);
+
+ quad(x5, y5, x6, y6, x7, y7, x8, y8);
+
+ extrude(x6, y6, x7, y7);
+ extrude(x8, y8, x5, y5);
+ }
+
+ for (int i = 0;i < vertices.size();i++)
+ vertices[i] *= 2.0f;
+}
+
+void Renderer::quad(qreal x1, qreal y1, qreal x2, qreal y2, qreal x3, qreal y3, qreal x4, qreal y4)
+{
+ vertices << QVector3D(x1, y1, -0.05f);
+ vertices << QVector3D(x2, y2, -0.05f);
+ vertices << QVector3D(x4, y4, -0.05f);
+
+ vertices << QVector3D(x3, y3, -0.05f);
+ vertices << QVector3D(x4, y4, -0.05f);
+ vertices << QVector3D(x2, y2, -0.05f);
+
+ QVector3D n = QVector3D::normal
+ (QVector3D(x2 - x1, y2 - y1, 0.0f), QVector3D(x4 - x1, y4 - y1, 0.0f));
+
+ normals << n;
+ normals << n;
+ normals << n;
+
+ normals << n;
+ normals << n;
+ normals << n;
+
+ vertices << QVector3D(x4, y4, 0.05f);
+ vertices << QVector3D(x2, y2, 0.05f);
+ vertices << QVector3D(x1, y1, 0.05f);
+
+ vertices << QVector3D(x2, y2, 0.05f);
+ vertices << QVector3D(x4, y4, 0.05f);
+ vertices << QVector3D(x3, y3, 0.05f);
+
+ n = QVector3D::normal
+ (QVector3D(x2 - x4, y2 - y4, 0.0f), QVector3D(x1 - x4, y1 - y4, 0.0f));
+
+ normals << n;
+ normals << n;
+ normals << n;
+
+ normals << n;
+ normals << n;
+ normals << n;
+}
+
+void Renderer::extrude(qreal x1, qreal y1, qreal x2, qreal y2)
+{
+ vertices << QVector3D(x1, y1, +0.05f);
+ vertices << QVector3D(x2, y2, +0.05f);
+ vertices << QVector3D(x1, y1, -0.05f);
+
+ vertices << QVector3D(x2, y2, -0.05f);
+ vertices << QVector3D(x1, y1, -0.05f);
+ vertices << QVector3D(x2, y2, +0.05f);
+
+ QVector3D n = QVector3D::normal
+ (QVector3D(x2 - x1, y2 - y1, 0.0f), QVector3D(0.0f, 0.0f, -0.1f));
+
+ normals << n;
+ normals << n;
+ normals << n;
+
+ normals << n;
+ normals << n;
+ normals << n;
+}
diff --git a/tests/manual/examples/opengl/hellowindow/hellowindow.h b/tests/manual/examples/opengl/hellowindow/hellowindow.h
new file mode 100644
index 0000000000..1e117ab2e0
--- /dev/null
+++ b/tests/manual/examples/opengl/hellowindow/hellowindow.h
@@ -0,0 +1,77 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef HELLOWINDOW_H
+#define HELLOWINDOW_H
+
+#include <QWindow>
+
+#include <QColor>
+#include <QMutex>
+#include <QOpenGLShaderProgram>
+#include <QOpenGLBuffer>
+#include <QSharedPointer>
+#include <QTimer>
+
+class HelloWindow;
+
+class Renderer : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit Renderer(const QSurfaceFormat &format, Renderer *share = nullptr,
+ QScreen *screen = nullptr);
+
+ QSurfaceFormat format() const { return m_format; }
+
+public slots:
+ void render(HelloWindow *surface, qreal angle, const QColor &color);
+
+signals:
+ void requestUpdate();
+
+private:
+ void initialize();
+
+ void createGeometry();
+ void createBubbles(int number);
+ void quad(qreal x1, qreal y1, qreal x2, qreal y2, qreal x3, qreal y3, qreal x4, qreal y4);
+ void extrude(qreal x1, qreal y1, qreal x2, qreal y2);
+
+ QList<QVector3D> vertices;
+ QList<QVector3D> normals;
+ int vertexAttr;
+ int normalAttr;
+ int matrixUniform;
+ int colorUniform;
+
+ bool m_initialized;
+ QSurfaceFormat m_format;
+ QOpenGLContext *m_context;
+ QOpenGLShaderProgram *m_program;
+ QOpenGLBuffer m_vbo;
+ QColor m_backgroundColor;
+};
+
+class HelloWindow : public QWindow
+{
+public:
+ explicit HelloWindow(const QSharedPointer<Renderer> &renderer, QScreen *screen = nullptr);
+
+ QColor color() const;
+ void updateColor();
+
+protected:
+ bool event(QEvent *ev) override;
+ void exposeEvent(QExposeEvent *event) override;
+ void mousePressEvent(QMouseEvent *) override;
+ void render();
+
+private:
+ int m_colorIndex;
+ QColor m_color;
+ const QSharedPointer<Renderer> m_renderer;
+};
+
+#endif // HELLOWINDOW_H
diff --git a/tests/manual/examples/opengl/hellowindow/hellowindow.pro b/tests/manual/examples/opengl/hellowindow/hellowindow.pro
new file mode 100644
index 0000000000..27c274a378
--- /dev/null
+++ b/tests/manual/examples/opengl/hellowindow/hellowindow.pro
@@ -0,0 +1,9 @@
+QT += gui-private core-private opengl
+
+HEADERS += hellowindow.h
+SOURCES += hellowindow.cpp main.cpp
+
+# install
+target.path = $$[QT_INSTALL_EXAMPLES]/opengl/hellowindow
+INSTALLS += target
+
diff --git a/tests/manual/examples/opengl/hellowindow/main.cpp b/tests/manual/examples/opengl/hellowindow/main.cpp
new file mode 100644
index 0000000000..ee4729a493
--- /dev/null
+++ b/tests/manual/examples/opengl/hellowindow/main.cpp
@@ -0,0 +1,125 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "hellowindow.h"
+
+#include <qpa/qplatformintegration.h>
+
+#include <QCommandLineParser>
+#include <QCommandLineOption>
+#include <QGuiApplication>
+#include <QScreen>
+#include <QThread>
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+ QCoreApplication::setApplicationName("Qt HelloWindow GL Example");
+ QCoreApplication::setOrganizationName("QtProject");
+ QCoreApplication::setApplicationVersion(QT_VERSION_STR);
+ QCommandLineParser parser;
+ parser.setApplicationDescription(QCoreApplication::applicationName());
+ parser.addHelpOption();
+ parser.addVersionOption();
+ QCommandLineOption multipleOption("multiple", "Create multiple windows");
+ parser.addOption(multipleOption);
+ QCommandLineOption multipleSampleOption("multisample", "Multisampling");
+ parser.addOption(multipleSampleOption);
+ QCommandLineOption multipleScreenOption("multiscreen", "Run on multiple screens");
+ parser.addOption(multipleScreenOption);
+ QCommandLineOption timeoutOption("timeout", "Close after 10s");
+ parser.addOption(timeoutOption);
+ parser.process(app);
+
+ // Some platforms can only have one window per screen. Therefore we need to differentiate.
+ const bool multipleWindows = parser.isSet(multipleOption);
+ const bool multipleScreens = parser.isSet(multipleScreenOption);
+
+ QScreen *screen = QGuiApplication::primaryScreen();
+
+ QRect screenGeometry = screen->availableGeometry();
+
+ QSurfaceFormat format;
+ format.setDepthBufferSize(16);
+ if (parser.isSet(multipleSampleOption))
+ format.setSamples(4);
+
+ QPoint center = QPoint(screenGeometry.center().x(), screenGeometry.top() + 80);
+ QSize windowSize(400, 320);
+ int delta = 40;
+
+ QList<QWindow *> windows;
+ QSharedPointer<Renderer> rendererA(new Renderer(format));
+
+ HelloWindow *windowA = new HelloWindow(rendererA);
+ windowA->setGeometry(QRect(center, windowSize).translated(-windowSize.width() - delta / 2, 0));
+ windowA->setTitle(QStringLiteral("Thread A - Context A"));
+ windowA->setVisible(true);
+ windows.prepend(windowA);
+
+ QList<QThread *> renderThreads;
+ if (multipleWindows) {
+ QSharedPointer<Renderer> rendererB(new Renderer(format, rendererA.data()));
+
+ QThread *renderThread = new QThread;
+ rendererB->moveToThread(renderThread);
+ renderThreads << renderThread;
+
+ HelloWindow *windowB = new HelloWindow(rendererA);
+ windowB->setGeometry(QRect(center, windowSize).translated(delta / 2, 0));
+ windowB->setTitle(QStringLiteral("Thread A - Context A"));
+ windowB->setVisible(true);
+ windows.prepend(windowB);
+
+ HelloWindow *windowC = new HelloWindow(rendererB);
+ windowC->setGeometry(QRect(center, windowSize).translated(-windowSize.width() / 2, windowSize.height() + delta));
+ windowC->setTitle(QStringLiteral("Thread B - Context B"));
+ windowC->setVisible(true);
+ windows.prepend(windowC);
+ }
+ if (multipleScreens) {
+ for (int i = 1; i < QGuiApplication::screens().size(); ++i) {
+ QScreen *screen = QGuiApplication::screens().at(i);
+ QSharedPointer<Renderer> renderer(new Renderer(format, rendererA.data(), screen));
+
+ QThread *renderThread = new QThread;
+ renderer->moveToThread(renderThread);
+ renderThreads.prepend(renderThread);
+
+ QRect screenGeometry = screen->availableGeometry();
+ QPoint center = screenGeometry.center();
+
+ QSize windowSize = screenGeometry.size() * 0.8;
+
+ HelloWindow *window = new HelloWindow(renderer, screen);
+ window->setGeometry(QRect(center, windowSize).translated(-windowSize.width() / 2, -windowSize.height() / 2));
+
+ QChar id = QChar('B' + i);
+ window->setTitle(QStringLiteral("Thread ") + id + QStringLiteral(" - Context ") + id);
+ window->setVisible(true);
+ windows.prepend(window);
+ }
+ }
+
+ for (int i = 0; i < renderThreads.size(); ++i) {
+ QObject::connect(qGuiApp, &QGuiApplication::lastWindowClosed, renderThreads.at(i), &QThread::quit);
+ renderThreads.at(i)->start();
+ }
+
+ // Quit after 10 seconds. For platforms that do not have windows that are closeable.
+ if (parser.isSet(timeoutOption))
+ QTimer::singleShot(10000, qGuiApp, &QCoreApplication::quit);
+
+ const int exitValue = app.exec();
+
+ for (int i = 0; i < renderThreads.size(); ++i) {
+ renderThreads.at(i)->quit(); // some platforms may not have windows to close so ensure quit()
+ renderThreads.at(i)->wait();
+ }
+
+ qDeleteAll(windows);
+ qDeleteAll(renderThreads);
+
+ return exitValue;
+}
diff --git a/tests/manual/examples/opengl/paintedwindow/CMakeLists.txt b/tests/manual/examples/opengl/paintedwindow/CMakeLists.txt
new file mode 100644
index 0000000000..c1bf33e73c
--- /dev/null
+++ b/tests/manual/examples/opengl/paintedwindow/CMakeLists.txt
@@ -0,0 +1,37 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(paintedwindow LANGUAGES CXX)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/opengl/paintedwindow")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui OpenGL)
+
+qt_standard_project_setup()
+
+qt_add_executable(paintedwindow
+ main.cpp
+ paintedwindow.cpp paintedwindow.h
+)
+
+set_target_properties(paintedwindow PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(paintedwindow PRIVATE
+ Qt6::Core
+ Qt6::Gui
+ Qt6::OpenGL
+)
+
+install(TARGETS paintedwindow
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/tests/manual/examples/opengl/paintedwindow/main.cpp b/tests/manual/examples/opengl/paintedwindow/main.cpp
new file mode 100644
index 0000000000..e055e92812
--- /dev/null
+++ b/tests/manual/examples/opengl/paintedwindow/main.cpp
@@ -0,0 +1,18 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QGuiApplication>
+#include <QRect>
+
+#include "paintedwindow.h"
+
+int main(int argc, char **argv)
+{
+ QGuiApplication app(argc, argv);
+
+ PaintedWindow window;
+ window.show();
+
+ return app.exec();
+}
+
diff --git a/tests/manual/examples/opengl/paintedwindow/paintedwindow.cpp b/tests/manual/examples/opengl/paintedwindow/paintedwindow.cpp
new file mode 100644
index 0000000000..d0b8284157
--- /dev/null
+++ b/tests/manual/examples/opengl/paintedwindow/paintedwindow.cpp
@@ -0,0 +1,185 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "paintedwindow.h"
+
+#include <QGuiApplication>
+#include <QOpenGLContext>
+#include <QOpenGLPaintDevice>
+#include <QPainter>
+#include <QPainterPath>
+#include <QScreen>
+#include <QTimer>
+
+#include <qmath.h>
+
+PaintedWindow::PaintedWindow()
+{
+ QSurfaceFormat format;
+ format.setStencilBufferSize(8);
+ format.setSamples(4);
+
+ setSurfaceType(QWindow::OpenGLSurface);
+ setFlags(Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint);
+ setFormat(format);
+
+ create();
+
+ m_context = new QOpenGLContext(this);
+ m_context->setFormat(format);
+ m_context->create();
+
+ m_animation = new QPropertyAnimation(this, "rotation");
+ m_animation->setStartValue(qreal(0));
+ m_animation->setEndValue(qreal(1));
+ m_animation->setDuration(500);
+
+ QRect screenGeometry = screen()->availableGeometry();
+
+ QPoint center = screenGeometry.center();
+ QRect windowRect = screen()->isLandscape(screen()->orientation()) ? QRect(0, 0, 640, 480) : QRect(0, 0, 480, 640);
+ setGeometry(QRect(center - windowRect.center(), windowRect.size()));
+
+ m_rotation = 0;
+
+ reportContentOrientationChange(screen()->orientation());
+
+ m_targetOrientation = contentOrientation();
+ m_nextTargetOrientation = Qt::PrimaryOrientation;
+
+ connect(screen(), &QScreen::orientationChanged, this, &PaintedWindow::orientationChanged);
+ connect(m_animation, &QAbstractAnimation::finished, this, &PaintedWindow::rotationDone);
+ connect(this, &PaintedWindow::rotationChanged, this, QOverload<>::of(&PaintedWindow::paint));
+}
+
+void PaintedWindow::exposeEvent(QExposeEvent *)
+{
+ if (isExposed())
+ paint();
+}
+
+void PaintedWindow::mousePressEvent(QMouseEvent *)
+{
+ Qt::ScreenOrientation o = contentOrientation();
+ switch (o) {
+ case Qt::LandscapeOrientation:
+ orientationChanged(Qt::PortraitOrientation);
+ break;
+ case Qt::PortraitOrientation:
+ orientationChanged(Qt::InvertedLandscapeOrientation);
+ break;
+ case Qt::InvertedLandscapeOrientation:
+ orientationChanged(Qt::InvertedPortraitOrientation);
+ break;
+ case Qt::InvertedPortraitOrientation:
+ orientationChanged(Qt::LandscapeOrientation);
+ break;
+ default:
+ Q_ASSERT(false);
+ }
+
+ paint();
+}
+
+void PaintedWindow::orientationChanged(Qt::ScreenOrientation newOrientation)
+{
+ if (contentOrientation() == newOrientation)
+ return;
+
+ if (m_animation->state() == QAbstractAnimation::Running) {
+ m_nextTargetOrientation = newOrientation;
+ return;
+ }
+
+ QRect rect(0, 0, width(), height());
+
+ m_prevImage = QImage(width(), height(), QImage::Format_ARGB32_Premultiplied);
+ m_nextImage = QImage(width(), height(), QImage::Format_ARGB32_Premultiplied);
+ m_prevImage.fill(0);
+ m_nextImage.fill(0);
+
+ QPainter p;
+ p.begin(&m_prevImage);
+ p.setTransform(screen()->transformBetween(contentOrientation(), screen()->orientation(), rect));
+ paint(&p, screen()->mapBetween(contentOrientation(), screen()->orientation(), rect));
+ p.end();
+
+ p.begin(&m_nextImage);
+ p.setTransform(screen()->transformBetween(newOrientation, screen()->orientation(), rect));
+ paint(&p, screen()->mapBetween(newOrientation, screen()->orientation(), rect));
+ p.end();
+
+ m_deltaRotation = screen()->angleBetween(newOrientation, contentOrientation());
+ if (m_deltaRotation > 180)
+ m_deltaRotation = 180 - m_deltaRotation;
+
+ m_targetOrientation = newOrientation;
+ m_animation->start();
+}
+
+void PaintedWindow::rotationDone()
+{
+ reportContentOrientationChange(m_targetOrientation);
+ if (m_nextTargetOrientation != Qt::PrimaryOrientation) {
+ Q_ASSERT(m_animation->state() != QAbstractAnimation::Running);
+ orientationChanged(m_nextTargetOrientation);
+ m_nextTargetOrientation = Qt::PrimaryOrientation;
+ }
+}
+
+void PaintedWindow::setRotation(qreal r)
+{
+ if (r != m_rotation) {
+ m_rotation = r;
+ emit rotationChanged(r);
+ }
+}
+
+void PaintedWindow::paint()
+{
+ m_context->makeCurrent(this);
+
+ QRect rect(0, 0, width() * devicePixelRatio(), height() * devicePixelRatio());
+
+ QOpenGLPaintDevice device(size() * devicePixelRatio());
+ QPainter painter(&device);
+
+ QPainterPath path;
+ path.addEllipse(rect);
+ painter.setCompositionMode(QPainter::CompositionMode_Source);
+ painter.fillRect(rect, Qt::transparent);
+ painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
+ painter.fillPath(path, Qt::blue);
+
+ if (contentOrientation() != m_targetOrientation) {
+ painter.setRenderHint(QPainter::SmoothPixmapTransform);
+ painter.save();
+ painter.translate(width() / 2, height() / 2);
+ painter.rotate(m_deltaRotation * m_rotation);
+ painter.translate(-width() / 2, -height() / 2);
+ painter.drawImage(0, 0, m_prevImage);
+ painter.restore();
+ painter.translate(width() / 2, height() / 2);
+ painter.rotate(m_deltaRotation * m_rotation - m_deltaRotation);
+ painter.translate(-width() / 2, -height() / 2);
+ painter.setOpacity(m_rotation);
+ painter.drawImage(0, 0, m_nextImage);
+ } else {
+ QRect mapped = screen()->mapBetween(contentOrientation(), screen()->orientation(), rect);
+
+ painter.setTransform(screen()->transformBetween(contentOrientation(), screen()->orientation(), rect));
+ paint(&painter, mapped);
+ painter.end();
+ }
+
+ m_context->swapBuffers(this);
+}
+
+void PaintedWindow::paint(QPainter *painter, const QRect &rect)
+{
+ painter->setRenderHint(QPainter::Antialiasing);
+ QFont font;
+ font.setPixelSize(64);
+ painter->setFont(font);
+ painter->drawText(rect, Qt::AlignCenter, "Hello");
+}
diff --git a/tests/manual/examples/opengl/paintedwindow/paintedwindow.h b/tests/manual/examples/opengl/paintedwindow/paintedwindow.h
new file mode 100644
index 0000000000..11a76a3056
--- /dev/null
+++ b/tests/manual/examples/opengl/paintedwindow/paintedwindow.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef PAINTEDWINDOW_H
+#define PAINTEDWINDOW_H
+
+#include <QWindow>
+
+#include <QtGui/qopengl.h>
+#include <QtOpenGL/qopenglshaderprogram.h>
+#include <QtOpenGL/qopenglframebufferobject.h>
+
+#include <QPropertyAnimation>
+
+#include <QColor>
+#include <QImage>
+#include <QTime>
+
+QT_BEGIN_NAMESPACE
+class QOpenGLContext;
+QT_END_NAMESPACE
+
+class PaintedWindow : public QWindow
+{
+ Q_OBJECT
+ Q_PROPERTY(qreal rotation READ rotation WRITE setRotation NOTIFY rotationChanged)
+
+public:
+ PaintedWindow();
+
+ qreal rotation() const { return m_rotation; }
+
+signals:
+ void rotationChanged(qreal rotation);
+
+private slots:
+ void paint();
+ void setRotation(qreal r);
+ void orientationChanged(Qt::ScreenOrientation newOrientation);
+ void rotationDone();
+
+private:
+ void exposeEvent(QExposeEvent *) override;
+ void mousePressEvent(QMouseEvent *) override;
+
+ void paint(QPainter *painter, const QRect &rect);
+
+ QOpenGLContext *m_context;
+ qreal m_rotation;
+
+ QImage m_prevImage;
+ QImage m_nextImage;
+ qreal m_deltaRotation;
+
+ Qt::ScreenOrientation m_targetOrientation;
+ Qt::ScreenOrientation m_nextTargetOrientation;
+
+ QPropertyAnimation *m_animation;
+ QTimer *m_paintTimer;
+};
+
+#endif // PAINTEDWINDOW_H
diff --git a/tests/manual/examples/opengl/paintedwindow/paintedwindow.pro b/tests/manual/examples/opengl/paintedwindow/paintedwindow.pro
new file mode 100644
index 0000000000..569eebe674
--- /dev/null
+++ b/tests/manual/examples/opengl/paintedwindow/paintedwindow.pro
@@ -0,0 +1,8 @@
+QT += opengl
+
+HEADERS += paintedwindow.h
+SOURCES += paintedwindow.cpp main.cpp
+
+# install
+target.path = $$[QT_INSTALL_EXAMPLES]/opengl/paintedwindow
+INSTALLS += target
diff --git a/tests/manual/examples/opengl/qopenglwidget/CMakeLists.txt b/tests/manual/examples/opengl/qopenglwidget/CMakeLists.txt
new file mode 100644
index 0000000000..d6e9c268ff
--- /dev/null
+++ b/tests/manual/examples/opengl/qopenglwidget/CMakeLists.txt
@@ -0,0 +1,53 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(qopenglwidget LANGUAGES CXX)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/opengl/qopenglwidget")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui OpenGL OpenGLWidgets Widgets)
+
+qt_standard_project_setup()
+
+qt_add_executable(qopenglwidget
+ bubble.cpp bubble.h
+ glwidget.cpp glwidget.h
+ main.cpp
+ mainwindow.cpp mainwindow.h
+)
+
+set_target_properties(qopenglwidget PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(qopenglwidget PRIVATE
+ Qt6::Core
+ Qt6::Gui
+ Qt6::OpenGL
+ Qt6::OpenGLWidgets
+ Qt6::Widgets
+)
+
+# Resources:
+set(texture_resource_files
+ "qt.png"
+)
+
+qt6_add_resources(qopenglwidget "texture"
+ PREFIX
+ "/"
+ FILES
+ ${texture_resource_files}
+)
+
+install(TARGETS qopenglwidget
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/tests/manual/examples/opengl/qopenglwidget/bubble.cpp b/tests/manual/examples/opengl/qopenglwidget/bubble.cpp
new file mode 100644
index 0000000000..3c32b62113
--- /dev/null
+++ b/tests/manual/examples/opengl/qopenglwidget/bubble.cpp
@@ -0,0 +1,99 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QtWidgets>
+
+#include "bubble.h"
+
+Bubble::Bubble(const QPointF &position, qreal radius, const QPointF &velocity)
+ : position(position), vel(velocity), radius(radius)
+{
+ innerColor = randomColor();
+ outerColor = randomColor();
+ updateBrush();
+}
+
+//! [0]
+void Bubble::updateCache()
+{
+ delete cache;
+ cache = new QImage(qRound(radius * 2 + 2), qRound(radius * 2 + 2), QImage::Format_ARGB32_Premultiplied);
+ cache->fill(0x00000000);
+ QPainter p(cache);
+ p.setRenderHint(QPainter::Antialiasing);
+ QPen pen(Qt::white);
+ pen.setWidth(2);
+ p.setPen(pen);
+ p.setBrush(brush);
+ p.drawEllipse(1, 1, int(2*radius), int(2*radius));
+}
+//! [0]
+
+Bubble::~Bubble()
+{
+ delete cache;
+}
+
+void Bubble::updateBrush()
+{
+ QRadialGradient gradient(QPointF(radius, radius), radius,
+ QPointF(radius*0.5, radius*0.5));
+
+ gradient.setColorAt(0, QColor(255, 255, 255, 255));
+ gradient.setColorAt(0.25, innerColor);
+ gradient.setColorAt(1, outerColor);
+ brush = QBrush(gradient);
+ updateCache();
+}
+
+//! [1]
+void Bubble::drawBubble(QPainter *painter)
+{
+ painter->save();
+ painter->translate(position.x() - radius, position.y() - radius);
+ painter->setOpacity(0.8);
+ painter->drawImage(0, 0, *cache);
+ painter->restore();
+}
+//! [1]
+
+QColor Bubble::randomColor()
+{
+ int red = int(185 + QRandomGenerator::global()->bounded(70));
+ int green = int(185 + QRandomGenerator::global()->bounded(70));
+ int blue = int(205 + QRandomGenerator::global()->bounded(50));
+ int alpha = int(91 + QRandomGenerator::global()->bounded(100));
+
+ return QColor(red, green, blue, alpha);
+}
+
+void Bubble::move(const QRect &bbox)
+{
+ position += vel;
+ qreal leftOverflow = position.x() - radius - bbox.left();
+ qreal rightOverflow = position.x() + radius - bbox.right();
+ qreal topOverflow = position.y() - radius - bbox.top();
+ qreal bottomOverflow = position.y() + radius - bbox.bottom();
+
+ if (leftOverflow < 0.0) {
+ position.setX(position.x() - 2 * leftOverflow);
+ vel.setX(-vel.x());
+ } else if (rightOverflow > 0.0) {
+ position.setX(position.x() - 2 * rightOverflow);
+ vel.setX(-vel.x());
+ }
+
+ if (topOverflow < 0.0) {
+ position.setY(position.y() - 2 * topOverflow);
+ vel.setY(-vel.y());
+ } else if (bottomOverflow > 0.0) {
+ position.setY(position.y() - 2 * bottomOverflow);
+ vel.setY(-vel.y());
+ }
+}
+
+QRectF Bubble::rect()
+{
+ return QRectF(position.x() - radius, position.y() - radius,
+ 2 * radius, 2 * radius);
+}
diff --git a/tests/manual/examples/opengl/qopenglwidget/bubble.h b/tests/manual/examples/opengl/qopenglwidget/bubble.h
new file mode 100644
index 0000000000..47355ead41
--- /dev/null
+++ b/tests/manual/examples/opengl/qopenglwidget/bubble.h
@@ -0,0 +1,39 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef BUBBLE_H
+#define BUBBLE_H
+
+#include <QBrush>
+#include <QColor>
+#include <QPointF>
+#include <QRect>
+#include <QRectF>
+
+QT_FORWARD_DECLARE_CLASS(QPainter)
+
+class Bubble
+{
+public:
+ Bubble(const QPointF &position, qreal radius, const QPointF &velocity);
+ ~Bubble();
+
+ void drawBubble(QPainter *painter);
+ void updateBrush();
+ void move(const QRect &bbox);
+ void updateCache();
+ QRectF rect();
+
+private:
+ QColor randomColor();
+
+ QBrush brush;
+ QPointF position;
+ QPointF vel;
+ qreal radius;
+ QColor innerColor;
+ QColor outerColor;
+ QImage *cache = nullptr;
+};
+
+#endif
diff --git a/tests/manual/examples/opengl/qopenglwidget/glwidget.cpp b/tests/manual/examples/opengl/qopenglwidget/glwidget.cpp
new file mode 100644
index 0000000000..e2a3ea4fc5
--- /dev/null
+++ b/tests/manual/examples/opengl/qopenglwidget/glwidget.cpp
@@ -0,0 +1,540 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "glwidget.h"
+#include <QPainter>
+#include <QPaintEngine>
+#include <QOpenGLShaderProgram>
+#include <QOpenGLTexture>
+#include <QRandomGenerator>
+#include <QCoreApplication>
+#include <qmath.h>
+
+#include "mainwindow.h"
+#include "bubble.h"
+
+const int bubbleNum = 8;
+
+#ifndef GL_SRGB8_ALPHA8
+#define GL_SRGB8_ALPHA8 0x8C43
+#endif
+
+GLWidget::GLWidget(MainWindow *maybeMainWindow, const QColor &background)
+ : m_mainWindow(maybeMainWindow),
+ m_background(background)
+{
+ setMinimumSize(300, 250);
+ if (QCoreApplication::arguments().contains(QStringLiteral("--srgb")))
+ setTextureFormat(GL_SRGB8_ALPHA8);
+}
+
+GLWidget::~GLWidget()
+{
+ reset();
+}
+
+void GLWidget::reset()
+{
+ qDeleteAll(m_bubbles);
+ // Leave everything in a state suitable for a subsequent call to
+ // initialize(). This matters when coming from the context's
+ // aboutToBeDestroyed signal, would not matter when invoked from the
+ // destructor.
+ m_bubbles.clear();
+
+ // And now release all OpenGL resources.
+ makeCurrent();
+ delete m_texture;
+ m_texture = nullptr;
+ delete m_program1;
+ m_program1 = nullptr;
+ delete m_program2;
+ m_program2 = nullptr;
+ delete m_vshader1;
+ m_vshader1 = nullptr;
+ delete m_fshader1;
+ m_fshader1 = nullptr;
+ delete m_vshader2;
+ m_vshader2 = nullptr;
+ delete m_fshader2;
+ m_fshader2 = nullptr;
+ m_vbo1.destroy();
+ m_vbo2.destroy();
+ doneCurrent();
+
+ // We are done with the current QOpenGLContext, forget it. If there is a
+ // subsequent initialize(), that will then connect to the new context.
+ QObject::disconnect(m_contextWatchConnection);
+}
+
+void GLWidget::setScaling(int scale)
+{
+ if (scale > 30)
+ m_fScale = 1 + qreal(scale - 30) / 30 * 0.25;
+ else if (scale < 30)
+ m_fScale = 1 - (qreal(30 - scale) / 30 * 0.25);
+ else
+ m_fScale = 1;
+}
+
+void GLWidget::setLogo()
+{
+ m_qtLogo = true;
+}
+
+void GLWidget::setTexture()
+{
+ m_qtLogo = false;
+}
+
+void GLWidget::setShowBubbles(bool bubbles)
+{
+ m_showBubbles = bubbles;
+}
+
+void GLWidget::paintQtLogo()
+{
+ m_program1->enableAttributeArray(m_vertexAttr1);
+ m_program1->enableAttributeArray(m_normalAttr1);
+
+ m_vbo1.bind();
+ // The data in the buffer is placed like this:
+ // vertex1.x, vertex1.y, vertex1.z, normal1.x, normal1.y, normal1.z, vertex2.x, ...
+ m_program1->setAttributeBuffer(m_vertexAttr1, GL_FLOAT, 0, 3, 6 * sizeof(GLfloat));
+ m_program1->setAttributeBuffer(m_normalAttr1, GL_FLOAT, 3 * sizeof(GLfloat), 3, 6 * sizeof(GLfloat));
+ m_vbo1.release();
+
+ glDrawArrays(GL_TRIANGLES, 0, m_vertices.size());
+
+ m_program1->disableAttributeArray(m_normalAttr1);
+ m_program1->disableAttributeArray(m_vertexAttr1);
+}
+
+void GLWidget::paintTexturedCube()
+{
+ m_texture->bind();
+
+ if (!m_vbo2.isCreated()) {
+ static GLfloat afVertices[] = {
+ -0.5, 0.5, 0.5, 0.5,-0.5,0.5,-0.5,-0.5,0.5,
+ 0.5, -0.5, 0.5, -0.5,0.5,0.5,0.5,0.5,0.5,
+ -0.5, -0.5, -0.5, 0.5,-0.5,-0.5,-0.5,0.5,-0.5,
+ 0.5, 0.5, -0.5, -0.5,0.5,-0.5,0.5,-0.5,-0.5,
+
+ 0.5, -0.5, -0.5, 0.5,-0.5,0.5,0.5,0.5,-0.5,
+ 0.5, 0.5, 0.5, 0.5,0.5,-0.5,0.5,-0.5,0.5,
+ -0.5, 0.5, -0.5, -0.5,-0.5,0.5,-0.5,-0.5,-0.5,
+ -0.5, -0.5, 0.5, -0.5,0.5,-0.5,-0.5,0.5,0.5,
+
+ 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5,
+ -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5,
+ -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5,
+ 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, -0.5, 0.5
+ };
+
+ static GLfloat afTexCoord[] = {
+ 0.0f,0.0f, 1.0f,1.0f, 1.0f,0.0f,
+ 1.0f,1.0f, 0.0f,0.0f, 0.0f,1.0f,
+ 1.0f,1.0f, 1.0f,0.0f, 0.0f,1.0f,
+ 0.0f,0.0f, 0.0f,1.0f, 1.0f,0.0f,
+
+ 1.0f,1.0f, 1.0f,0.0f, 0.0f,1.0f,
+ 0.0f,0.0f, 0.0f,1.0f, 1.0f,0.0f,
+ 0.0f,0.0f, 1.0f,1.0f, 1.0f,0.0f,
+ 1.0f,1.0f, 0.0f,0.0f, 0.0f,1.0f,
+
+ 0.0f,1.0f, 1.0f,0.0f, 1.0f,1.0f,
+ 1.0f,0.0f, 0.0f,1.0f, 0.0f,0.0f,
+ 1.0f,0.0f, 1.0f,1.0f, 0.0f,0.0f,
+ 0.0f,1.0f, 0.0f,0.0f, 1.0f,1.0f
+ };
+
+ GLfloat afNormals[] = {
+
+ 0,0,-1, 0,0,-1, 0,0,-1,
+ 0,0,-1, 0,0,-1, 0,0,-1,
+ 0,0,1, 0,0,1, 0,0,1,
+ 0,0,1, 0,0,1, 0,0,1,
+
+ -1,0,0, -1,0,0, -1,0,0,
+ -1,0,0, -1,0,0, -1,0,0,
+ 1,0,0, 1,0,0, 1,0,0,
+ 1,0,0, 1,0,0, 1,0,0,
+
+ 0,-1,0, 0,-1,0, 0,-1,0,
+ 0,-1,0, 0,-1,0, 0,-1,0,
+ 0,1,0, 0,1,0, 0,1,0,
+ 0,1,0, 0,1,0, 0,1,0
+ };
+
+ m_vbo2.create();
+ m_vbo2.bind();
+ m_vbo2.allocate(36 * 8 * sizeof(GLfloat));
+ m_vbo2.write(0, afVertices, sizeof(afVertices));
+ m_vbo2.write(sizeof(afVertices), afTexCoord, sizeof(afTexCoord));
+ m_vbo2.write(sizeof(afVertices) + sizeof(afTexCoord), afNormals, sizeof(afNormals));
+ m_vbo2.release();
+ }
+
+ m_program2->setUniformValue(m_textureUniform2, 0); // use texture unit 0
+
+ m_program2->enableAttributeArray(m_vertexAttr2);
+ m_program2->enableAttributeArray(m_normalAttr2);
+ m_program2->enableAttributeArray(m_texCoordAttr2);
+
+ m_vbo2.bind();
+ // In the buffer we first have 36 vertices (3 floats for each), then 36 texture
+ // coordinates (2 floats for each), then 36 normals (3 floats for each).
+ m_program2->setAttributeBuffer(m_vertexAttr2, GL_FLOAT, 0, 3);
+ m_program2->setAttributeBuffer(m_texCoordAttr2, GL_FLOAT, 36 * 3 * sizeof(GLfloat), 2);
+ m_program2->setAttributeBuffer(m_normalAttr2, GL_FLOAT, 36 * 5 * sizeof(GLfloat), 3);
+ m_vbo2.release();
+
+ glDrawArrays(GL_TRIANGLES, 0, 36);
+
+ m_program2->disableAttributeArray(m_vertexAttr2);
+ m_program2->disableAttributeArray(m_normalAttr2);
+ m_program2->disableAttributeArray(m_texCoordAttr2);
+}
+
+void GLWidget::initializeGL()
+{
+ initializeOpenGLFunctions();
+
+ m_texture = new QOpenGLTexture(QImage(":/qt.png"));
+
+ m_vshader1 = new QOpenGLShader(QOpenGLShader::Vertex);
+ const char *vsrc1 =
+ "attribute highp vec4 vertex;\n"
+ "attribute mediump vec3 normal;\n"
+ "uniform mediump mat4 matrix;\n"
+ "varying mediump vec4 color;\n"
+ "void main(void)\n"
+ "{\n"
+ " vec3 toLight = normalize(vec3(0.0, 0.3, 1.0));\n"
+ " float angle = max(dot(normal, toLight), 0.0);\n"
+ " vec3 col = vec3(0.40, 1.0, 0.0);\n"
+ " color = vec4(col * 0.2 + col * 0.8 * angle, 1.0);\n"
+ " color = clamp(color, 0.0, 1.0);\n"
+ " gl_Position = matrix * vertex;\n"
+ "}\n";
+ m_vshader1->compileSourceCode(vsrc1);
+
+ m_fshader1 = new QOpenGLShader(QOpenGLShader::Fragment);
+ const char *fsrc1 =
+ "varying mediump vec4 color;\n"
+ "void main(void)\n"
+ "{\n"
+ " gl_FragColor = color;\n"
+ "}\n";
+ m_fshader1->compileSourceCode(fsrc1);
+
+ m_program1 = new QOpenGLShaderProgram;
+ m_program1->addShader(m_vshader1);
+ m_program1->addShader(m_fshader1);
+ m_program1->link();
+
+ m_vertexAttr1 = m_program1->attributeLocation("vertex");
+ m_normalAttr1 = m_program1->attributeLocation("normal");
+ m_matrixUniform1 = m_program1->uniformLocation("matrix");
+
+ m_vshader2 = new QOpenGLShader(QOpenGLShader::Vertex);
+ const char *vsrc2 =
+ "attribute highp vec4 vertex;\n"
+ "attribute highp vec4 texCoord;\n"
+ "attribute mediump vec3 normal;\n"
+ "uniform mediump mat4 matrix;\n"
+ "varying highp vec4 texc;\n"
+ "varying mediump float angle;\n"
+ "void main(void)\n"
+ "{\n"
+ " vec3 toLight = normalize(vec3(0.0, 0.3, 1.0));\n"
+ " angle = max(dot(normal, toLight), 0.0);\n"
+ " gl_Position = matrix * vertex;\n"
+ " texc = texCoord;\n"
+ "}\n";
+ m_vshader2->compileSourceCode(vsrc2);
+
+ m_fshader2 = new QOpenGLShader(QOpenGLShader::Fragment);
+ const char *fsrc2 =
+ "varying highp vec4 texc;\n"
+ "uniform sampler2D tex;\n"
+ "varying mediump float angle;\n"
+ "void main(void)\n"
+ "{\n"
+ " highp vec3 color = texture2D(tex, texc.st).rgb;\n"
+ " color = color * 0.2 + color * 0.8 * angle;\n"
+ " gl_FragColor = vec4(clamp(color, 0.0, 1.0), 1.0);\n"
+ "}\n";
+ m_fshader2->compileSourceCode(fsrc2);
+
+ m_program2 = new QOpenGLShaderProgram;
+ m_program2->addShader(m_vshader2);
+ m_program2->addShader(m_fshader2);
+ m_program2->link();
+
+ m_vertexAttr2 = m_program2->attributeLocation("vertex");
+ m_normalAttr2 = m_program2->attributeLocation("normal");
+ m_texCoordAttr2 = m_program2->attributeLocation("texCoord");
+ m_matrixUniform2 = m_program2->uniformLocation("matrix");
+ m_textureUniform2 = m_program2->uniformLocation("tex");
+
+ m_fAngle = 0;
+ m_fScale = 1;
+
+ createGeometry();
+
+ // Use a vertex buffer object. Client-side pointers are old-school and should be avoided.
+ m_vbo1.create();
+ m_vbo1.bind();
+ // For the cube all the data belonging to the texture coordinates and
+ // normals is placed separately, after the vertices. Here, for the Qt logo,
+ // let's do something different and potentially more efficient: create a
+ // properly interleaved data set.
+ const int vertexCount = m_vertices.count();
+ QList<GLfloat> buf;
+ buf.resize(vertexCount * 3 * 2);
+ GLfloat *p = buf.data();
+ for (int i = 0; i < vertexCount; ++i) {
+ *p++ = m_vertices[i].x();
+ *p++ = m_vertices[i].y();
+ *p++ = m_vertices[i].z();
+ *p++ = m_normals[i].x();
+ *p++ = m_normals[i].y();
+ *p++ = m_normals[i].z();
+ }
+ m_vbo1.allocate(buf.constData(), buf.count() * sizeof(GLfloat));
+ m_vbo1.release();
+
+ createBubbles(bubbleNum - m_bubbles.count());
+
+ // A well-behaved QOpenGLWidget releases OpenGL resources not only upon
+ // destruction, but also when the associated OpenGL context disappears. If
+ // the widget continues to exist, the context's destruction will be
+ // followed by a call to initialize(). This is not strictly mandatory in
+ // widgets that never change their parents.
+ m_contextWatchConnection = QObject::connect(context(), &QOpenGLContext::aboutToBeDestroyed, context(), [this] { reset(); });
+}
+
+void GLWidget::paintGL()
+{
+ createBubbles(bubbleNum - m_bubbles.count());
+
+ QPainter painter;
+ painter.begin(this);
+
+ painter.beginNativePainting();
+
+ glClearColor(m_background.redF(), m_background.greenF(), m_background.blueF(), m_transparent ? 0.0f : 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ glFrontFace(GL_CW);
+ glCullFace(GL_FRONT);
+ glEnable(GL_CULL_FACE);
+ glEnable(GL_DEPTH_TEST);
+
+ QMatrix4x4 modelview;
+ modelview.rotate(m_fAngle, 0.0f, 1.0f, 0.0f);
+ modelview.rotate(m_fAngle, 1.0f, 0.0f, 0.0f);
+ modelview.rotate(m_fAngle, 0.0f, 0.0f, 1.0f);
+ modelview.scale(m_fScale);
+ modelview.translate(0.0f, -0.2f, 0.0f);
+
+ if (m_qtLogo) {
+ m_program1->bind();
+ m_program1->setUniformValue(m_matrixUniform1, modelview);
+ paintQtLogo();
+ m_program1->release();
+ } else {
+ m_program2->bind();
+ m_program2->setUniformValue(m_matrixUniform2, modelview);
+ paintTexturedCube();
+ m_program2->release();
+ }
+
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_CULL_FACE);
+
+ painter.endNativePainting();
+
+ if (m_showBubbles) {
+ for (Bubble *bubble : std::as_const(m_bubbles))
+ bubble->drawBubble(&painter);
+ }
+
+ if (const int elapsed = m_time.elapsed()) {
+ QString framesPerSecond;
+ framesPerSecond.setNum(m_frames /(elapsed / 1000.0), 'f', 2);
+ painter.setPen(m_transparent ? Qt::black : Qt::white);
+ painter.drawText(20, 40, framesPerSecond + " paintGL calls / s");
+ }
+
+ painter.end();
+
+ for (Bubble *bubble : std::as_const(m_bubbles))
+ bubble->move(rect());
+
+ if (!(m_frames % 100)) {
+ m_time.start();
+ m_frames = 0;
+ }
+ m_fAngle += 1.0f;
+ ++m_frames;
+
+ // When requested, follow the ideal way to animate: Rely on
+ // blocking swap and just schedule updates continuously.
+ if (!m_mainWindow || !m_mainWindow->timerEnabled())
+ update();
+}
+
+void GLWidget::createBubbles(int number)
+{
+ for (int i = 0; i < number; ++i) {
+ QPointF position(width()*(0.1 + QRandomGenerator::global()->bounded(0.8)),
+ height()*(0.1 + QRandomGenerator::global()->bounded(0.8)));
+ qreal radius = qMin(width(), height())*(0.0175 + QRandomGenerator::global()->bounded(0.0875));
+ QPointF velocity(width()*0.0175*(-0.5 + QRandomGenerator::global()->bounded(1.0)),
+ height()*0.0175*(-0.5 + QRandomGenerator::global()->bounded(1.0)));
+
+ m_bubbles.append(new Bubble(position, radius, velocity));
+ }
+}
+
+void GLWidget::createGeometry()
+{
+ m_vertices.clear();
+ m_normals.clear();
+
+ qreal x1 = +0.06f;
+ qreal y1 = -0.14f;
+ qreal x2 = +0.14f;
+ qreal y2 = -0.06f;
+ qreal x3 = +0.08f;
+ qreal y3 = +0.00f;
+ qreal x4 = +0.30f;
+ qreal 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;
+ const qreal sectorAngle = 2 * qreal(M_PI) / NumSectors;
+
+ for (int i = 0; i < NumSectors; ++i) {
+ qreal angle = i * sectorAngle;
+ qreal x5 = 0.30 * sin(angle);
+ qreal y5 = 0.30 * cos(angle);
+ qreal x6 = 0.20 * sin(angle);
+ qreal y6 = 0.20 * cos(angle);
+
+ angle += sectorAngle;
+ qreal x7 = 0.20 * sin(angle);
+ qreal y7 = 0.20 * cos(angle);
+ qreal x8 = 0.30 * sin(angle);
+ qreal y8 = 0.30 * cos(angle);
+
+ quad(x5, y5, x6, y6, x7, y7, x8, y8);
+
+ extrude(x6, y6, x7, y7);
+ extrude(x8, y8, x5, y5);
+ }
+
+ for (int i = 0;i < m_vertices.size();i++)
+ m_vertices[i] *= 2.0f;
+}
+
+void GLWidget::quad(qreal x1, qreal y1, qreal x2, qreal y2, qreal x3, qreal y3, qreal x4, qreal y4)
+{
+ m_vertices << QVector3D(x1, y1, -0.05f);
+ m_vertices << QVector3D(x2, y2, -0.05f);
+ m_vertices << QVector3D(x4, y4, -0.05f);
+
+ m_vertices << QVector3D(x3, y3, -0.05f);
+ m_vertices << QVector3D(x4, y4, -0.05f);
+ m_vertices << QVector3D(x2, y2, -0.05f);
+
+ QVector3D n = QVector3D::normal
+ (QVector3D(x2 - x1, y2 - y1, 0.0f), QVector3D(x4 - x1, y4 - y1, 0.0f));
+
+ m_normals << n;
+ m_normals << n;
+ m_normals << n;
+
+ m_normals << n;
+ m_normals << n;
+ m_normals << n;
+
+ m_vertices << QVector3D(x4, y4, 0.05f);
+ m_vertices << QVector3D(x2, y2, 0.05f);
+ m_vertices << QVector3D(x1, y1, 0.05f);
+
+ m_vertices << QVector3D(x2, y2, 0.05f);
+ m_vertices << QVector3D(x4, y4, 0.05f);
+ m_vertices << QVector3D(x3, y3, 0.05f);
+
+ n = QVector3D::normal
+ (QVector3D(x2 - x4, y2 - y4, 0.0f), QVector3D(x1 - x4, y1 - y4, 0.0f));
+
+ m_normals << n;
+ m_normals << n;
+ m_normals << n;
+
+ m_normals << n;
+ m_normals << n;
+ m_normals << n;
+}
+
+void GLWidget::extrude(qreal x1, qreal y1, qreal x2, qreal y2)
+{
+ m_vertices << QVector3D(x1, y1, +0.05f);
+ m_vertices << QVector3D(x2, y2, +0.05f);
+ m_vertices << QVector3D(x1, y1, -0.05f);
+
+ m_vertices << QVector3D(x2, y2, -0.05f);
+ m_vertices << QVector3D(x1, y1, -0.05f);
+ m_vertices << QVector3D(x2, y2, +0.05f);
+
+ QVector3D n = QVector3D::normal
+ (QVector3D(x2 - x1, y2 - y1, 0.0f), QVector3D(0.0f, 0.0f, -0.1f));
+
+ m_normals << n;
+ m_normals << n;
+ m_normals << n;
+
+ m_normals << n;
+ m_normals << n;
+ m_normals << n;
+}
+
+void GLWidget::setTransparent(bool transparent)
+{
+ setAttribute(Qt::WA_AlwaysStackOnTop, transparent);
+ m_transparent = transparent;
+ // Call update() on the top-level window after toggling AlwayStackOnTop to make sure
+ // the entire backingstore is updated accordingly.
+ window()->update();
+}
+
+void GLWidget::resizeGL(int, int)
+{
+ if (m_mainWindow) {
+ if (!m_btn) {
+ m_btn = new QPushButton("\nAdd widget\n", this);
+ connect(m_btn, &QPushButton::clicked, this, [this] { m_mainWindow->addNew(); });
+ }
+ m_btn->move(20, 80);
+ if (!m_btn2) {
+ m_btn2 = new QPushButton("\nI prefer tabbed widgets\n", this);
+ connect(m_btn2, &QPushButton::clicked, this, [this] { m_mainWindow->showNewWindow(); });
+ }
+ m_btn2->move(20, 160);
+ }
+}
diff --git a/tests/manual/examples/opengl/qopenglwidget/glwidget.h b/tests/manual/examples/opengl/qopenglwidget/glwidget.h
new file mode 100644
index 0000000000..de9d11b94c
--- /dev/null
+++ b/tests/manual/examples/opengl/qopenglwidget/glwidget.h
@@ -0,0 +1,85 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef GLWIDGET_H
+#define GLWIDGET_H
+
+#include <QOpenGLWidget>
+#include <QOpenGLFunctions>
+#include <QOpenGLBuffer>
+#include <QVector3D>
+#include <QMatrix4x4>
+#include <QElapsedTimer>
+#include <QList>
+#include <QPushButton>
+
+class Bubble;
+class MainWindow;
+
+QT_FORWARD_DECLARE_CLASS(QOpenGLTexture)
+QT_FORWARD_DECLARE_CLASS(QOpenGLShader)
+QT_FORWARD_DECLARE_CLASS(QOpenGLShaderProgram)
+
+class GLWidget : public QOpenGLWidget, protected QOpenGLFunctions
+{
+ Q_OBJECT
+public:
+ GLWidget(MainWindow *maybeMainWindow, const QColor &background);
+ ~GLWidget();
+
+public slots:
+ void setScaling(int scale);
+ void setLogo();
+ void setTexture();
+ void setShowBubbles(bool);
+ void setTransparent(bool transparent);
+
+protected:
+ void resizeGL(int w, int h) override;
+ void paintGL() override;
+ void initializeGL() override;
+
+private:
+ void paintTexturedCube();
+ void paintQtLogo();
+ void createGeometry();
+ void createBubbles(int number);
+ void quad(qreal x1, qreal y1, qreal x2, qreal y2, qreal x3, qreal y3, qreal x4, qreal y4);
+ void extrude(qreal x1, qreal y1, qreal x2, qreal y2);
+ void reset();
+
+ MainWindow *m_mainWindow;
+ qreal m_fAngle = 0;
+ qreal m_fScale = 1;
+ bool m_showBubbles = true;
+ QList<QVector3D> m_vertices;
+ QList<QVector3D> m_normals;
+ bool m_qtLogo = true;
+ QList<Bubble *> m_bubbles;
+ int m_frames = 0;
+ QElapsedTimer m_time;
+ QOpenGLShader *m_vshader1 = nullptr;
+ QOpenGLShader *m_fshader1 = nullptr;
+ QOpenGLShader *m_vshader2 = nullptr;
+ QOpenGLShader *m_fshader2 = nullptr;
+ QOpenGLShaderProgram *m_program1 = nullptr;
+ QOpenGLShaderProgram *m_program2 = nullptr;
+ QOpenGLTexture *m_texture = nullptr;
+ QOpenGLBuffer m_vbo1;
+ QOpenGLBuffer m_vbo2;
+ int m_vertexAttr1 = 0;
+ int m_normalAttr1 = 0;
+ int m_matrixUniform1 = 0;
+ int m_vertexAttr2 = 0;
+ int m_normalAttr2 = 0;
+ int m_texCoordAttr2 = 0;
+ int m_matrixUniform2 = 0;
+ int m_textureUniform2 = 0;
+ bool m_transparent = false;
+ QPushButton *m_btn = nullptr;
+ QPushButton *m_btn2 = nullptr;
+ QColor m_background;
+ QMetaObject::Connection m_contextWatchConnection;
+};
+
+#endif
diff --git a/tests/manual/examples/opengl/qopenglwidget/main.cpp b/tests/manual/examples/opengl/qopenglwidget/main.cpp
new file mode 100644
index 0000000000..1614649a5a
--- /dev/null
+++ b/tests/manual/examples/opengl/qopenglwidget/main.cpp
@@ -0,0 +1,42 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QApplication>
+#include <QMainWindow>
+#include <QColorSpace>
+#include <QSurfaceFormat>
+#include <QCommandLineParser>
+#include <QCommandLineOption>
+#include "mainwindow.h"
+
+int main( int argc, char ** argv )
+{
+ QApplication a( argc, argv );
+
+ QCoreApplication::setApplicationName("Qt QOpenGLWidget Example");
+ QCoreApplication::setOrganizationName("QtProject");
+ QCoreApplication::setApplicationVersion(QT_VERSION_STR);
+ QCommandLineParser parser;
+ parser.setApplicationDescription(QCoreApplication::applicationName());
+ parser.addHelpOption();
+ parser.addVersionOption();
+ QCommandLineOption multipleSampleOption("multisample", "Multisampling");
+ parser.addOption(multipleSampleOption);
+ QCommandLineOption srgbOption("srgb", "Use sRGB Color Space");
+ parser.addOption(srgbOption);
+ parser.process(a);
+
+ QSurfaceFormat format;
+ format.setDepthBufferSize(24);
+ format.setStencilBufferSize(8);
+ if (parser.isSet(srgbOption))
+ format.setColorSpace(QColorSpace::SRgb);
+ if (parser.isSet(multipleSampleOption))
+ format.setSamples(4);
+ QSurfaceFormat::setDefaultFormat(format);
+
+ MainWindow mw;
+ mw.resize(1280, 720);
+ mw.show();
+ return a.exec();
+}
diff --git a/tests/manual/examples/opengl/qopenglwidget/mainwindow.cpp b/tests/manual/examples/opengl/qopenglwidget/mainwindow.cpp
new file mode 100644
index 0000000000..c383a692d5
--- /dev/null
+++ b/tests/manual/examples/opengl/qopenglwidget/mainwindow.cpp
@@ -0,0 +1,188 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "mainwindow.h"
+
+#include <QApplication>
+#include <QMenuBar>
+#include <QGroupBox>
+#include <QSlider>
+#include <QLabel>
+#include <QCheckBox>
+#include <QRandomGenerator>
+#include <QSpinBox>
+#include <QScrollArea>
+#include <QTabWidget>
+#include <QTabBar>
+#include <QToolButton>
+
+#include "glwidget.h"
+
+MainWindow::MainWindow()
+ : m_nextX(1), m_nextY(1)
+{
+ GLWidget *glwidget = new GLWidget(this, qRgb(20, 20, 50));
+ m_glWidgets << glwidget;
+ QLabel *label = new QLabel(this);
+ m_timer = new QTimer(this);
+ QSlider *slider = new QSlider(this);
+ slider->setOrientation(Qt::Horizontal);
+
+ QLabel *updateLabel = new QLabel("Update interval");
+ QSpinBox *updateInterval = new QSpinBox(this);
+ updateInterval->setSuffix(" ms");
+ updateInterval->setValue(10);
+ updateInterval->setToolTip("Interval for the timer that calls update().\n"
+ "Note that on most systems the swap will block to wait for vsync\n"
+ "and therefore an interval < 16 ms will likely lead to a 60 FPS update rate.");
+ QGroupBox *updateGroupBox = new QGroupBox(this);
+ QCheckBox *timerBased = new QCheckBox("Use timer", this);
+ timerBased->setChecked(false);
+ timerBased->setToolTip("Toggles using a timer to trigger update().\n"
+ "When not set, each paintGL() schedules the next update immediately,\n"
+ "expecting the blocking swap to throttle the thread.\n"
+ "This shows how unnecessary the timer is in most cases.");
+ QCheckBox *transparent = new QCheckBox("Transparent background", this);
+ transparent->setToolTip("Toggles Qt::WA_AlwaysStackOnTop and transparent clear color for glClear().\n"
+ "Note how the button on top stacks incorrectly when enabling this.");
+ QHBoxLayout *updateLayout = new QHBoxLayout;
+ updateLayout->addWidget(updateLabel);
+ updateLayout->addWidget(updateInterval);
+ updateLayout->addWidget(timerBased);
+ updateLayout->addWidget(transparent);
+ updateGroupBox->setLayout(updateLayout);
+
+ slider->setRange(0, 50);
+ slider->setSliderPosition(30);
+ m_timer->setInterval(10);
+ label->setText("A scrollable QOpenGLWidget");
+ label->setAlignment(Qt::AlignHCenter);
+
+ QGroupBox * groupBox = new QGroupBox(this);
+ setCentralWidget(groupBox);
+ groupBox->setTitle("QOpenGLWidget Example");
+
+ m_layout = new QGridLayout(groupBox);
+
+ QScrollArea *scrollArea = new QScrollArea;
+ scrollArea->setWidget(glwidget);
+
+ m_layout->addWidget(scrollArea,1,0,8,1);
+ m_layout->addWidget(label,9,0,1,1);
+ m_layout->addWidget(updateGroupBox, 10, 0, 1, 1);
+ m_layout->addWidget(slider, 11,0,1,1);
+
+ groupBox->setLayout(m_layout);
+
+
+ QMenu *fileMenu = menuBar()->addMenu("&File");
+ fileMenu->addAction("E&xit", this, &QWidget::close);
+ QMenu *showMenu = menuBar()->addMenu("&Show");
+ showMenu->addAction("Show 3D Logo", glwidget, &GLWidget::setLogo);
+ showMenu->addAction("Show 2D Texture", glwidget, &GLWidget::setTexture);
+ QAction *showBubbles = showMenu->addAction("Show bubbles", glwidget, &GLWidget::setShowBubbles);
+ showBubbles->setCheckable(true);
+ showBubbles->setChecked(true);
+ showMenu->addAction("Open tab window", this, &MainWindow::showNewWindow);
+ QMenu *helpMenu = menuBar()->addMenu("&Help");
+ helpMenu->addAction("About Qt", qApp, &QApplication::aboutQt);
+
+ connect(m_timer, &QTimer::timeout, glwidget, QOverload<>::of(&QWidget::update));
+
+ connect(slider, &QAbstractSlider::valueChanged, glwidget, &GLWidget::setScaling);
+ connect(transparent, &QCheckBox::toggled, glwidget, &GLWidget::setTransparent);
+ connect(updateInterval, &QSpinBox::valueChanged,
+ this, &MainWindow::updateIntervalChanged);
+ connect(timerBased, &QCheckBox::toggled, this, &MainWindow::timerUsageChanged);
+ connect(timerBased, &QCheckBox::toggled, updateInterval, &QWidget::setEnabled);
+
+ if (timerBased->isChecked())
+ m_timer->start();
+ else
+ updateInterval->setEnabled(false);
+}
+
+void MainWindow::updateIntervalChanged(int value)
+{
+ m_timer->setInterval(value);
+ if (m_timer->isActive())
+ m_timer->start();
+}
+
+void MainWindow::addNew()
+{
+ if (m_nextY == 4)
+ return;
+ GLWidget *w = new GLWidget(nullptr, qRgb(QRandomGenerator::global()->bounded(256),
+ QRandomGenerator::global()->bounded(256),
+ QRandomGenerator::global()->bounded(256)));
+ m_glWidgets << w;
+ connect(m_timer, &QTimer::timeout, w, QOverload<>::of(&QWidget::update));
+ m_layout->addWidget(w, m_nextY, m_nextX, 1, 1);
+ if (m_nextX == 3) {
+ m_nextX = 1;
+ ++m_nextY;
+ } else {
+ ++m_nextX;
+ }
+}
+
+void MainWindow::timerUsageChanged(bool enabled)
+{
+ if (enabled) {
+ m_timer->start();
+ } else {
+ m_timer->stop();
+ for (QOpenGLWidget *w : std::as_const(m_glWidgets))
+ w->update();
+ }
+}
+
+void MainWindow::resizeEvent(QResizeEvent *)
+{
+ m_glWidgets[0]->setMinimumSize(size() + QSize(128, 128));
+}
+
+void MainWindow::showNewWindow()
+{
+ QTabWidget *tabs = new QTabWidget;
+ tabs->resize(800, 600);
+
+ QToolButton *tb = new QToolButton;
+ tb->setText(QLatin1String("+"));
+ tabs->addTab(new QLabel(QLatin1String("Add OpenGL widgets with +")), QString());
+ tabs->setTabEnabled(0, false);
+ tabs->tabBar()->setTabButton(0, QTabBar::RightSide, tb);
+ tabs->tabBar()->setTabsClosable(true);
+ QObject::connect(tabs->tabBar(), &QTabBar::tabCloseRequested, tabs, [tabs](int index) {
+ tabs->widget(index)->deleteLater();
+ });
+
+ const QString msgToTopLevel = QLatin1String("Break out to top-level window");
+ const QString msgFromTopLevel = QLatin1String("Move back under tab widget");
+
+ QObject::connect(tb, &QAbstractButton::clicked, tabs, [=] {
+ GLWidget *glwidget = new GLWidget(nullptr, Qt::blue);
+ glwidget->resize(tabs->size());
+ glwidget->setWindowTitle(QString::asprintf("QOpenGLWidget %p", glwidget));
+
+ QPushButton *btn = new QPushButton(msgToTopLevel, glwidget);
+ connect(btn, &QPushButton::clicked, glwidget, [=] {
+ if (glwidget->parent()) {
+ glwidget->setAttribute(Qt::WA_DeleteOnClose, true);
+ glwidget->setParent(nullptr);
+ glwidget->show();
+ btn->setText(msgFromTopLevel);
+ } else {
+ glwidget->setAttribute(Qt::WA_DeleteOnClose, false);
+ tabs->addTab(glwidget, glwidget->windowTitle());
+ btn->setText(msgToTopLevel);
+ }
+ });
+
+ tabs->setCurrentIndex(tabs->addTab(glwidget, glwidget->windowTitle()));
+ });
+
+ tabs->setAttribute(Qt::WA_DeleteOnClose);
+ tabs->show();
+}
diff --git a/tests/manual/examples/opengl/qopenglwidget/mainwindow.h b/tests/manual/examples/opengl/qopenglwidget/mainwindow.h
new file mode 100644
index 0000000000..85f11ad764
--- /dev/null
+++ b/tests/manual/examples/opengl/qopenglwidget/mainwindow.h
@@ -0,0 +1,39 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+#include <QTimer>
+#include <QGridLayout>
+
+QT_FORWARD_DECLARE_CLASS(QOpenGLWidget)
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ MainWindow();
+ void addNew();
+ bool timerEnabled() const { return m_timer->isActive(); }
+
+ void resizeEvent(QResizeEvent *);
+
+public slots:
+ void showNewWindow();
+
+private slots:
+ void updateIntervalChanged(int value);
+ void timerUsageChanged(bool enabled);
+
+private:
+ QTimer *m_timer;
+ QGridLayout *m_layout;
+ int m_nextX;
+ int m_nextY;
+ QList<QOpenGLWidget *> m_glWidgets;
+};
+
+#endif
diff --git a/tests/manual/examples/opengl/qopenglwidget/qopenglwidget.pro b/tests/manual/examples/opengl/qopenglwidget/qopenglwidget.pro
new file mode 100644
index 0000000000..7ac546d7ce
--- /dev/null
+++ b/tests/manual/examples/opengl/qopenglwidget/qopenglwidget.pro
@@ -0,0 +1,15 @@
+QT += widgets opengl openglwidgets
+
+SOURCES += main.cpp \
+ glwidget.cpp \
+ mainwindow.cpp \
+ bubble.cpp
+
+HEADERS += glwidget.h \
+ mainwindow.h \
+ bubble.h
+
+RESOURCES += texture.qrc
+
+target.path = $$[QT_INSTALL_EXAMPLES]/opengl/qopenglwidget
+INSTALLS += target
diff --git a/tests/manual/examples/opengl/qopenglwidget/qt.png b/tests/manual/examples/opengl/qopenglwidget/qt.png
new file mode 100644
index 0000000000..4f68e162de
--- /dev/null
+++ b/tests/manual/examples/opengl/qopenglwidget/qt.png
Binary files differ
diff --git a/tests/manual/examples/opengl/qopenglwidget/texture.qrc b/tests/manual/examples/opengl/qopenglwidget/texture.qrc
new file mode 100644
index 0000000000..ff1d0e535f
--- /dev/null
+++ b/tests/manual/examples/opengl/qopenglwidget/texture.qrc
@@ -0,0 +1,5 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource>
+ <file>qt.png</file>
+</qresource>
+</RCC>
diff --git a/tests/manual/examples/opengl/qopenglwindow/CMakeLists.txt b/tests/manual/examples/opengl/qopenglwindow/CMakeLists.txt
new file mode 100644
index 0000000000..be3cb45152
--- /dev/null
+++ b/tests/manual/examples/opengl/qopenglwindow/CMakeLists.txt
@@ -0,0 +1,49 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(qopenglwindow LANGUAGES CXX)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/opengl/qopenglwindow")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui OpenGL)
+
+qt_standard_project_setup()
+
+qt_add_executable(qopenglwindow
+ background_renderer.cpp background_renderer.h
+ main.cpp
+)
+
+set_target_properties(qopenglwindow PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(qopenglwindow PRIVATE
+ Qt6::Core
+ Qt6::Gui
+ Qt6::OpenGL
+)
+
+# Resources:
+set(shaders_resource_files
+ "background.frag"
+)
+
+qt_add_resources(qopenglwindow "shaders"
+ PREFIX
+ "/"
+ FILES
+ ${shaders_resource_files}
+)
+
+install(TARGETS qopenglwindow
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/tests/manual/examples/opengl/qopenglwindow/background.frag b/tests/manual/examples/opengl/qopenglwindow/background.frag
new file mode 100644
index 0000000000..4af85aae8e
--- /dev/null
+++ b/tests/manual/examples/opengl/qopenglwindow/background.frag
@@ -0,0 +1,30 @@
+#define M_PI 3.14159265358979323846
+#define SPEED 10000.0
+
+uniform int currentTime;
+uniform highp vec2 windowSize;
+
+highp float noise(highp vec2 co)
+{
+ return 0.5 * fract(sin(dot(co.xy, vec2(12.9898,78.233))) * 43758.5453);
+}
+
+highp float curvSpeed()
+{
+ return (mod(float(currentTime), SPEED) / SPEED) * (2.0 * M_PI);
+}
+
+highp float curv(int curvCount)
+{
+ highp float curv_y = 0.1 *(cos((gl_FragCoord.x / windowSize.x) * (float(curvCount * 2) * M_PI) - curvSpeed())) + 0.5;
+ highp float frag_y = gl_FragCoord.y / windowSize.y;
+ return 1.0 - abs(curv_y - frag_y);
+}
+
+void main()
+{
+ highp float coordNoise = noise(gl_FragCoord.xy);
+ highp float proximity = smoothstep(0.85, 1.0, (curv(6) + 1.0) * (coordNoise ));
+ highp vec3 color = vec3(coordNoise) * proximity;
+ gl_FragColor = vec4(color, 1.0);
+}
diff --git a/tests/manual/examples/opengl/qopenglwindow/background_renderer.cpp b/tests/manual/examples/opengl/qopenglwindow/background_renderer.cpp
new file mode 100644
index 0000000000..5939894dfe
--- /dev/null
+++ b/tests/manual/examples/opengl/qopenglwindow/background_renderer.cpp
@@ -0,0 +1,167 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "background_renderer.h"
+
+#include <qmath.h>
+#include <QFileInfo>
+#include <QTime>
+
+#include <QOpenGLShaderProgram>
+#include <QOpenGLContext>
+#include <QOpenGLFunctions>
+
+#include <math.h>
+
+static const char vertex_shader[] =
+ "attribute highp vec3 vertexCoord;"
+ "void main() {"
+ " gl_Position = vec4(vertexCoord,1.0);"
+ "}";
+
+static const char fragment_shader[] =
+ "void main() {"
+ " gl_FragColor = vec4(0.0,1.0,0.0,1.0);"
+ "}";
+
+static const float vertices[] = { -1, -1, 0,
+ -1, 1, 0,
+ 1, -1, 0,
+ 1, 1, 0 };
+
+FragmentToy::FragmentToy(const QString &fragmentSource, QObject *parent)
+ : QObject(parent)
+ , m_recompile_shaders(true)
+{
+ if (QFile::exists(fragmentSource)) {
+ QFileInfo info(fragmentSource);
+ m_fragment_file_last_modified = info.lastModified();
+ m_fragment_file = fragmentSource;
+#if QT_CONFIG(filesystemwatcher)
+ m_watcher.addPath(info.canonicalPath());
+ QObject::connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FragmentToy::fileChanged);
+#endif
+ }
+}
+
+FragmentToy::~FragmentToy()
+ = default;
+
+void FragmentToy::draw(const QSize &windowSize)
+{
+ if (!m_program)
+ initializeOpenGLFunctions();
+
+ glDisable(GL_STENCIL_TEST);
+ glDisable(GL_DEPTH_TEST);
+
+ glClearColor(0, 0, 0, 1);
+ glClear(GL_COLOR_BUFFER_BIT);
+ if (!m_vao.isCreated())
+ m_vao.create();
+
+ QOpenGLVertexArrayObject::Binder binder(&m_vao);
+
+ if (!m_vertex_buffer.isCreated()) {
+ m_vertex_buffer.create();
+ m_vertex_buffer.bind();
+ m_vertex_buffer.allocate(vertices, sizeof(vertices));
+ m_vertex_buffer.release();
+ }
+
+ if (!m_program) {
+ m_program.reset(new QOpenGLShaderProgram);
+ m_program->create();
+ m_vertex_shader.reset(new QOpenGLShader(QOpenGLShader::Vertex));
+ if (!m_vertex_shader->compileSourceCode(vertex_shader)) {
+ qWarning() << "Failed to compile the vertex shader:" << m_vertex_shader->log();
+ }
+ if (!m_program->addShader(m_vertex_shader.get())) {
+ qWarning() << "Failed to add vertex shader to program:" << m_program->log();
+ }
+ }
+
+ if (!m_fragment_shader && m_recompile_shaders) {
+ QByteArray data;
+ if (m_fragment_file.size()) {
+ QFile file(m_fragment_file);
+ if (file.open(QIODevice::ReadOnly)) {
+ data = file.readAll();
+ } else {
+ qWarning() << "Failed to load input file, falling back to default";
+ data = QByteArray::fromRawData(fragment_shader, sizeof(fragment_shader));
+ }
+ } else {
+ QFile qrcFile(":/background.frag");
+ if (qrcFile.open(QIODevice::ReadOnly))
+ data = qrcFile.readAll();
+ else
+ data = QByteArray::fromRawData(fragment_shader, sizeof(fragment_shader));
+ }
+ if (data.size()) {
+ m_fragment_shader.reset(new QOpenGLShader(QOpenGLShader::Fragment));
+ if (!m_fragment_shader->compileSourceCode(data)) {
+ qWarning() << "Failed to compile fragment shader:" << m_fragment_shader->log();
+ m_fragment_shader.reset(nullptr);
+ }
+ } else {
+ qWarning() << "Unknown error, no fragment shader";
+ }
+
+ if (m_fragment_shader) {
+ if (!m_program->addShader(m_fragment_shader.get())) {
+ qWarning() << "Failed to add fragment shader to program:" << m_program->log();
+ }
+ }
+ }
+
+ if (m_recompile_shaders) {
+ m_recompile_shaders = false;
+
+ if (m_program->link()) {
+ m_vertex_coord_pos = m_program->attributeLocation("vertexCoord");
+ } else {
+ qWarning() << "Failed to link shader program" << m_program->log();
+ }
+
+ }
+
+ if (!m_program->isLinked())
+ return;
+
+ m_program->bind();
+
+ m_vertex_buffer.bind();
+ m_program->setAttributeBuffer("vertexCoord", GL_FLOAT, 0, 3, 0);
+ m_program->enableAttributeArray("vertexCoord");
+ m_vertex_buffer.release();
+
+ m_program->setUniformValue("currentTime", (uint) QDateTime::currentMSecsSinceEpoch());
+ m_program->setUniformValue("windowSize", windowSize);
+
+ QOpenGLContext::currentContext()->functions()->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+ m_program->release();
+}
+
+void FragmentToy::fileChanged(const QString &path)
+{
+ Q_UNUSED(path);
+ if (QFile::exists(m_fragment_file)) {
+ QFileInfo fragment_source(m_fragment_file);
+ if (fragment_source.lastModified() > m_fragment_file_last_modified) {
+ m_fragment_file_last_modified = fragment_source.lastModified();
+ m_recompile_shaders = true;
+ if (m_program) {
+ m_program->removeShader(m_fragment_shader.get());
+ m_fragment_shader.reset(nullptr);
+ }
+ }
+ } else {
+ m_recompile_shaders = true;
+ if (m_program) {
+ m_program->removeShader(m_fragment_shader.get());
+ m_fragment_shader.reset(nullptr);
+ }
+ }
+}
diff --git a/tests/manual/examples/opengl/qopenglwindow/background_renderer.h b/tests/manual/examples/opengl/qopenglwindow/background_renderer.h
new file mode 100644
index 0000000000..76c0bec6a8
--- /dev/null
+++ b/tests/manual/examples/opengl/qopenglwindow/background_renderer.h
@@ -0,0 +1,46 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef FRAGMENT_TOY_H
+#define FRAGMENT_TOY_H
+
+#include <QObject>
+#include <QFile>
+#include <QDateTime>
+#if QT_CONFIG(filesystemwatcher)
+#include <QFileSystemWatcher>
+#endif
+#include <QOpenGLVertexArrayObject>
+#include <QOpenGLBuffer>
+#include <QOpenGLShaderProgram>
+#include <QOpenGLFunctions>
+
+#include <memory>
+
+class FragmentToy : public QObject, protected QOpenGLFunctions
+{
+ Q_OBJECT
+public:
+ explicit FragmentToy(const QString &fragmentSource, QObject *parent = nullptr);
+ ~FragmentToy();
+
+ void draw(const QSize &windowSize);
+
+private:
+ void fileChanged(const QString &path);
+ bool m_recompile_shaders;
+#if QT_CONFIG(filesystemwatcher)
+ QFileSystemWatcher m_watcher;
+#endif
+ QString m_fragment_file;
+ QDateTime m_fragment_file_last_modified;
+
+ std::unique_ptr<QOpenGLShaderProgram> m_program;
+ std::unique_ptr<QOpenGLShader> m_vertex_shader;
+ std::unique_ptr<QOpenGLShader> m_fragment_shader;
+ QOpenGLVertexArrayObject m_vao;
+ QOpenGLBuffer m_vertex_buffer;
+ GLuint m_vertex_coord_pos;
+};
+
+#endif //FRAGMENT_TOY_H
diff --git a/tests/manual/examples/opengl/qopenglwindow/main.cpp b/tests/manual/examples/opengl/qopenglwindow/main.cpp
new file mode 100644
index 0000000000..44810a4ecf
--- /dev/null
+++ b/tests/manual/examples/opengl/qopenglwindow/main.cpp
@@ -0,0 +1,154 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QOpenGLWindow>
+#include <QScreen>
+#include <QPainter>
+#include <QPainterPath>
+#include <QGuiApplication>
+#include <QMatrix4x4>
+#include <QStaticText>
+#include <QKeyEvent>
+
+#include "background_renderer.h"
+
+static QPainterPath painterPathForTriangle()
+{
+ static const QPointF bottomLeft(-1.0, -1.0);
+ static const QPointF top(0.0, 1.0);
+ static const QPointF bottomRight(1.0, -1.0);
+
+ QPainterPath path(bottomLeft);
+ path.lineTo(top);
+ path.lineTo(bottomRight);
+ path.closeSubpath();
+ return path;
+}
+
+class OpenGLWindow : public QOpenGLWindow
+{
+ Q_OBJECT
+
+public:
+ OpenGLWindow();
+
+protected:
+ void paintGL() override;
+ void resizeGL(int w, int h) override;
+ void keyPressEvent(QKeyEvent *e) override;
+
+private:
+ void setAnimating(bool enabled);
+
+ QMatrix4x4 m_window_normalised_matrix;
+ QMatrix4x4 m_window_painter_matrix;
+ QMatrix4x4 m_projection;
+ QMatrix4x4 m_view;
+ QMatrix4x4 m_model_triangle;
+ QMatrix4x4 m_model_text;
+
+ FragmentToy m_fragment_toy;
+ QStaticText m_text_layout;
+ bool m_animate;
+};
+
+// Use NoPartialUpdate. This means that all the rendering goes directly to
+// the window surface, no additional framebuffer object stands in the
+// middle. This is fine since we will clear the entire framebuffer on each
+// paint. Under the hood this means that the behavior is equivalent to the
+// manual makeCurrent - perform OpenGL calls - swapBuffers loop that is
+// typical in pure QWindow-based applications.
+OpenGLWindow::OpenGLWindow()
+ : QOpenGLWindow(QOpenGLWindow::NoPartialUpdate)
+ , m_fragment_toy("./background.frag")
+ , m_text_layout("The triangle and this text is rendered with QPainter")
+ , m_animate(true)
+{
+ setGeometry(300, 300, 500, 500);
+
+ m_view.lookAt(QVector3D(3,1,1),
+ QVector3D(0,0,0),
+ QVector3D(0,1,0));
+
+ setAnimating(m_animate);
+}
+
+void OpenGLWindow::paintGL()
+{
+ m_fragment_toy.draw(size());
+
+ QPainter p(this);
+ p.setWorldTransform(m_window_normalised_matrix.toTransform());
+
+ QMatrix4x4 mvp = m_projection * m_view * m_model_triangle;
+ p.setTransform(mvp.toTransform(), true);
+
+ p.fillPath(painterPathForTriangle(), QBrush(QGradient(QGradient::NightFade)));
+
+ QTransform text_transform = (m_window_painter_matrix * m_view * m_model_text).toTransform();
+ p.setTransform(text_transform, false);
+ p.setPen(QPen(Qt::black));
+ m_text_layout.prepare(text_transform);
+ qreal x = - (m_text_layout.size().width() / 2);
+ qreal y = 0;
+ p.drawStaticText(x, y, m_text_layout);
+
+ m_model_triangle.rotate(-1, 0, 1, 0);
+ m_model_text.rotate(1, 0, 1, 0);
+}
+
+void OpenGLWindow::resizeGL(int w, int h)
+{
+ m_window_normalised_matrix.setToIdentity();
+ m_window_normalised_matrix.translate(w / 2.0, h / 2.0);
+ m_window_normalised_matrix.scale(w / 2.0, -h / 2.0);
+
+ m_window_painter_matrix.setToIdentity();
+ m_window_painter_matrix.translate(w / 2.0, h / 2.0);
+
+ m_text_layout.setTextWidth(std::max(w * 0.2, 80.0));
+
+ m_projection.setToIdentity();
+ m_projection.perspective(45.f, qreal(w) / qreal(h), 0.1f, 100.f);
+}
+
+void OpenGLWindow::keyPressEvent(QKeyEvent *e)
+{
+ if (e->key() == Qt::Key_P) { // pause
+ m_animate = !m_animate;
+ setAnimating(m_animate);
+ }
+}
+
+void OpenGLWindow::setAnimating(bool enabled)
+{
+ if (enabled) {
+ // Animate continuously, throttled by the blocking swapBuffers() call the
+ // QOpenGLWindow internally executes after each paint. Once that is done
+ // (frameSwapped signal is emitted), we schedule a new update. This
+ // obviously assumes that the swap interval (see
+ // QSurfaceFormat::setSwapInterval()) is non-zero.
+ connect(this, &QOpenGLWindow::frameSwapped,
+ this, QOverload<>::of(&QPaintDeviceWindow::update));
+ update();
+ } else {
+ disconnect(this, &QOpenGLWindow::frameSwapped,
+ this, QOverload<>::of(&QPaintDeviceWindow::update));
+ }
+}
+
+int main(int argc, char **argv)
+{
+ QGuiApplication app(argc, argv);
+
+ OpenGLWindow window;
+ QSurfaceFormat fmt;
+ fmt.setDepthBufferSize(24);
+ fmt.setStencilBufferSize(8);
+ window.setFormat(fmt);
+ window.show();
+
+ return app.exec();
+}
+
+#include "main.moc"
diff --git a/tests/manual/examples/opengl/qopenglwindow/qopenglwindow.pro b/tests/manual/examples/opengl/qopenglwindow/qopenglwindow.pro
new file mode 100644
index 0000000000..e7073a324b
--- /dev/null
+++ b/tests/manual/examples/opengl/qopenglwindow/qopenglwindow.pro
@@ -0,0 +1,16 @@
+TEMPLATE = app
+TARGET = qopenglwindow
+INCLUDEPATH += .
+QT += opengl
+
+RESOURCES += shaders.qrc
+
+SOURCES += \
+ main.cpp \
+ background_renderer.cpp
+
+HEADERS += \
+ background_renderer.h
+
+target.path = $$[QT_INSTALL_EXAMPLES]/opengl/qopenglwindow
+INSTALLS += target
diff --git a/tests/manual/examples/opengl/qopenglwindow/shaders.qrc b/tests/manual/examples/opengl/qopenglwindow/shaders.qrc
new file mode 100644
index 0000000000..64eefe2c70
--- /dev/null
+++ b/tests/manual/examples/opengl/qopenglwindow/shaders.qrc
@@ -0,0 +1,5 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource>
+ <file>background.frag</file>
+</qresource>
+</RCC>