summaryrefslogtreecommitdiffstats
path: root/tests/manual
diff options
context:
space:
mode:
authorLaszlo Agocs <laszlo.agocs@qt.io>2021-10-25 15:21:31 +0200
committerTor Arne Vestbø <tor.arne.vestbo@qt.io>2022-03-11 21:25:00 +0100
commit68a4c5da9a080101cccd8a3b2edb1c908da0ca8e (patch)
treee89a8c02539147d30cb7529441d1a9e055163760 /tests/manual
parent8ccf1080fcb131940939369271e6828b178b8e9e (diff)
Compose render-to-texture widgets through QRhi
QPlatformTextureList holds a QRhiTexture instead of GLuint. A QPlatformBackingStore now optionally can own a QRhi and a QRhiSwapChain for the associated window. Non-GL rendering must use this QRhi everywhere, whereas GL (QOpenGLWidget) can choose to still rely on resource sharing between contexts. A widget tells that it wants QRhi and the desired configuration in a new virtual function in QWidgetPrivate returning a QPlatformBackingStoreRhiConfig. This is evaluated (among a top-level's all children) upon create() before creating the repaint manager and the QWidgetWindow. In QOpenGLWidget what do request is obvious: it will request an OpenGL-based QRhi. QQuickWidget (or a potential future QRhiWidget) will be more interesting: it needs to honor the standard Qt Quick env.vars. and QQuickWindow APIs (or, in whatever way the user configured the QRhiWidget), and so will set up the config struct accordingly. In addition, the rhiconfig and surface type is (re)evaluated when (re)parenting a widget to a new tlw. If needed, this will now trigger a destroy - create on the tlw. This should be be safe to do in setParent. When multiple child widgets report an enabled rhiconfig, the first one (the first child encountered) wins. So e.g. attempting to have a QOpenGLWidget and a Vulkan-based QQuickWidget in the same top-level window will fail one of the widgets (it likely won't render). RasterGLSurface is no longer used by widgets. Rather, the appropriate surface type is chosen. The rhi support in the backingstore is usable without widgets as well. To make rhiFlush() functional, one needs to call setRhiConfig() after creating the QBackingStore. (like QWidget does to top-level windows) Most of the QT_NO_OPENGL ifdefs are eliminated all over the place. Everything with QRhi is unconditional code at compile time, except the actual initialization. Having to plumb the widget tlw's shareContext (or, now, the QRhi) through QWindowPrivate is no longer needed. The old approach does not scale: to implement composeAndFlush (now rhiFlush) we need more than just a QRhi object, and this way we no longer pollute everything starting from the widget level (QWidget's topextra -> QWidgetWindow -> QWindowPrivate) just to send data around. The BackingStoreOpenGLSupport interface and the QtGui - QtOpenGL split is all gone. Instead, there is a QBackingStoreDefaultCompositor in QtGui which is what the default implementations of composeAndFlush and toTexture call. (overriding composeAndFlush and co. f.ex. in eglfs should continue working mostly as-is, apart from adapting to the texture list changes and getting the native OpenGL texture id out of the QRhiTexture) As QQuickWidget is way too complicated to just port as-is, an rhi manual test (rhiwidget) is introduced as a first step, in ordewr to exercise a simple, custom render-to-texture widget that does something using a (not necessarily OpenGL-backed) QRhi and acts as fully functional QWidget (modeled after QOpenGLWidget). This can also form the foundation of a potential future QRhiWidget. It is also possible to force the QRhi-based flushing always, regardless of the presence of render-to-texture widgets. To exercise this, set the env.var. QT_WIDGETS_RHI=1. This picks a platform-specific default, and can be overridden with QT_WIDGETS_RHI_BACKEND. (in sync with Qt Quick) This can eventually be extended to query the platform plugin as well to check if the platform plugin prefers to always do flushes with a 3D API. QOpenGLWidget should work like before from the user's perspective, while internally it has to do some things differently to play nice and prevent regressions with the new rendering architecture. To exercise this better, the qopenglwidget example gets a new tab-based view (that could perhaps replace the example's main window later on?). The openglwidget manual test is made compatible with Qt 6, and gets a counterpart in form of the dockedopenglwidget manual test, which is a modified version of the cube example that features dock widgets. This is relevant in particular because render-to-texture widgets within a QDockWidget has its own specific quirks, with logic taking this into account, hence testing is essential. For existing applications there are two important consequences with this patch in place: - Once the rhi-based composition is enabled, it stays active for the lifetime of the top-level window. - Dynamically creating and parenting the first render-to-texture widget to an already created tlw will destroy and recreate the tlw (and the underlying window). The visible effects of this depend on the platform. (e.g. the window may disappear and reappear on some, whereas with other windowing systems it is not noticeable at all - this is not really different from similar situtions with reparenting or when moving windows between screens, so should be acceptable in practice) - On iOS raster windows are flushed with Metal (and rhi) from now on (previously this was through OpenGL by making flush() call composeAndFlush(). Change-Id: Id05bd0f7a26fa845f8b7ad8eedda3b0e78ab7a4e Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
Diffstat (limited to 'tests/manual')
-rw-r--r--tests/manual/qopenglwidget/dockedopenglwidget/CMakeLists.txt28
-rw-r--r--tests/manual/qopenglwidget/dockedopenglwidget/cube.pngbin0 -> 30341 bytes
-rw-r--r--tests/manual/qopenglwidget/dockedopenglwidget/fshader.glsl8
-rw-r--r--tests/manual/qopenglwidget/dockedopenglwidget/geometryengine.cpp151
-rw-r--r--tests/manual/qopenglwidget/dockedopenglwidget/geometryengine.h51
-rw-r--r--tests/manual/qopenglwidget/dockedopenglwidget/main.cpp39
-rw-r--r--tests/manual/qopenglwidget/dockedopenglwidget/mainwidget.cpp186
-rw-r--r--tests/manual/qopenglwidget/dockedopenglwidget/mainwidget.h82
-rw-r--r--tests/manual/qopenglwidget/dockedopenglwidget/mainwindow.cpp56
-rw-r--r--tests/manual/qopenglwidget/dockedopenglwidget/mainwindow.h49
-rw-r--r--tests/manual/qopenglwidget/dockedopenglwidget/mainwindow.ui33
-rw-r--r--tests/manual/qopenglwidget/dockedopenglwidget/vshader.glsl12
-rw-r--r--tests/manual/qopenglwidget/openglwidget/CMakeLists.txt2
-rw-r--r--tests/manual/qopenglwidget/openglwidget/openglwidget.cpp2
-rw-r--r--tests/manual/qopenglwidget/openglwidget/openglwidget.h2
-rw-r--r--tests/manual/rhi/CMakeLists.txt3
-rw-r--r--tests/manual/rhi/rhiwidget/CMakeLists.txt30
-rw-r--r--tests/manual/rhi/rhiwidget/examplewidget.cpp229
-rw-r--r--tests/manual/rhi/rhiwidget/examplewidget.h113
-rw-r--r--tests/manual/rhi/rhiwidget/main.cpp144
-rw-r--r--tests/manual/rhi/rhiwidget/rhiwidget.cpp592
-rw-r--r--tests/manual/rhi/rhiwidget/rhiwidget.h103
-rw-r--r--tests/manual/rhi/rhiwidget/rhiwidget_p.h79
23 files changed, 1992 insertions, 2 deletions
diff --git a/tests/manual/qopenglwidget/dockedopenglwidget/CMakeLists.txt b/tests/manual/qopenglwidget/dockedopenglwidget/CMakeLists.txt
new file mode 100644
index 0000000000..5bc28599a6
--- /dev/null
+++ b/tests/manual/qopenglwidget/dockedopenglwidget/CMakeLists.txt
@@ -0,0 +1,28 @@
+set(CMAKE_AUTOUIC ON)
+
+qt_internal_add_manual_test(dockedopenglwidget
+ GUI
+ SOURCES
+ main.cpp
+ geometryengine.cpp geometryengine.h
+ mainwidget.cpp mainwidget.h
+ mainwindow.cpp mainwindow.h
+ mainwindow.ui
+ PUBLIC_LIBRARIES
+ Qt::CorePrivate
+ Qt::Gui
+ Qt::GuiPrivate
+ Qt::OpenGL
+ Qt::OpenGLWidgets
+ Qt::Widgets
+ Qt::WidgetsPrivate
+)
+
+qt_add_resources(dockedopenglwidget "dockedopenglwidget"
+ PREFIX
+ "/"
+ FILES
+ vshader.glsl
+ fshader.glsl
+ cube.png
+)
diff --git a/tests/manual/qopenglwidget/dockedopenglwidget/cube.png b/tests/manual/qopenglwidget/dockedopenglwidget/cube.png
new file mode 100644
index 0000000000..42c8c51b3a
--- /dev/null
+++ b/tests/manual/qopenglwidget/dockedopenglwidget/cube.png
Binary files differ
diff --git a/tests/manual/qopenglwidget/dockedopenglwidget/fshader.glsl b/tests/manual/qopenglwidget/dockedopenglwidget/fshader.glsl
new file mode 100644
index 0000000000..8e5a4de8fc
--- /dev/null
+++ b/tests/manual/qopenglwidget/dockedopenglwidget/fshader.glsl
@@ -0,0 +1,8 @@
+uniform sampler2D texture;
+varying vec2 v_texcoord;
+
+void main()
+{
+ gl_FragColor = texture2D(texture, v_texcoord);
+}
+
diff --git a/tests/manual/qopenglwidget/dockedopenglwidget/geometryengine.cpp b/tests/manual/qopenglwidget/dockedopenglwidget/geometryengine.cpp
new file mode 100644
index 0000000000..f68b849bed
--- /dev/null
+++ b/tests/manual/qopenglwidget/dockedopenglwidget/geometryengine.cpp
@@ -0,0 +1,151 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "geometryengine.h"
+
+#include <QVector2D>
+#include <QVector3D>
+
+struct VertexData
+{
+ QVector3D position;
+ QVector2D texCoord;
+};
+
+GeometryEngine::GeometryEngine()
+ : indexBuf(QOpenGLBuffer::IndexBuffer)
+{
+ initializeOpenGLFunctions();
+
+ // Generate 2 VBOs
+ arrayBuf.create();
+ indexBuf.create();
+
+ // Initializes cube geometry and transfers it to VBOs
+ initCubeGeometry();
+}
+
+GeometryEngine::~GeometryEngine()
+{
+ arrayBuf.destroy();
+ indexBuf.destroy();
+}
+
+void GeometryEngine::initCubeGeometry()
+{
+ // For cube we would need only 8 vertices but we have to
+ // duplicate vertex for each face because texture coordinate
+ // is different.
+ VertexData vertices[] = {
+ // Vertex data for face 0
+ {QVector3D(-1.0f, -1.0f, 1.0f), QVector2D(0.0f, 0.0f)}, // v0
+ {QVector3D( 1.0f, -1.0f, 1.0f), QVector2D(0.33f, 0.0f)}, // v1
+ {QVector3D(-1.0f, 1.0f, 1.0f), QVector2D(0.0f, 0.5f)}, // v2
+ {QVector3D( 1.0f, 1.0f, 1.0f), QVector2D(0.33f, 0.5f)}, // v3
+
+ // Vertex data for face 1
+ {QVector3D( 1.0f, -1.0f, 1.0f), QVector2D( 0.0f, 0.5f)}, // v4
+ {QVector3D( 1.0f, -1.0f, -1.0f), QVector2D(0.33f, 0.5f)}, // v5
+ {QVector3D( 1.0f, 1.0f, 1.0f), QVector2D(0.0f, 1.0f)}, // v6
+ {QVector3D( 1.0f, 1.0f, -1.0f), QVector2D(0.33f, 1.0f)}, // v7
+
+ // Vertex data for face 2
+ {QVector3D( 1.0f, -1.0f, -1.0f), QVector2D(0.66f, 0.5f)}, // v8
+ {QVector3D(-1.0f, -1.0f, -1.0f), QVector2D(1.0f, 0.5f)}, // v9
+ {QVector3D( 1.0f, 1.0f, -1.0f), QVector2D(0.66f, 1.0f)}, // v10
+ {QVector3D(-1.0f, 1.0f, -1.0f), QVector2D(1.0f, 1.0f)}, // v11
+
+ // Vertex data for face 3
+ {QVector3D(-1.0f, -1.0f, -1.0f), QVector2D(0.66f, 0.0f)}, // v12
+ {QVector3D(-1.0f, -1.0f, 1.0f), QVector2D(1.0f, 0.0f)}, // v13
+ {QVector3D(-1.0f, 1.0f, -1.0f), QVector2D(0.66f, 0.5f)}, // v14
+ {QVector3D(-1.0f, 1.0f, 1.0f), QVector2D(1.0f, 0.5f)}, // v15
+
+ // Vertex data for face 4
+ {QVector3D(-1.0f, -1.0f, -1.0f), QVector2D(0.33f, 0.0f)}, // v16
+ {QVector3D( 1.0f, -1.0f, -1.0f), QVector2D(0.66f, 0.0f)}, // v17
+ {QVector3D(-1.0f, -1.0f, 1.0f), QVector2D(0.33f, 0.5f)}, // v18
+ {QVector3D( 1.0f, -1.0f, 1.0f), QVector2D(0.66f, 0.5f)}, // v19
+
+ // Vertex data for face 5
+ {QVector3D(-1.0f, 1.0f, 1.0f), QVector2D(0.33f, 0.5f)}, // v20
+ {QVector3D( 1.0f, 1.0f, 1.0f), QVector2D(0.66f, 0.5f)}, // v21
+ {QVector3D(-1.0f, 1.0f, -1.0f), QVector2D(0.33f, 1.0f)}, // v22
+ {QVector3D( 1.0f, 1.0f, -1.0f), QVector2D(0.66f, 1.0f)} // v23
+ };
+
+ // Indices for drawing cube faces using triangle strips.
+ // Triangle strips can be connected by duplicating indices
+ // between the strips. If connecting strips have opposite
+ // vertex order then last index of the first strip and first
+ // index of the second strip needs to be duplicated. If
+ // connecting strips have same vertex order then only last
+ // index of the first strip needs to be duplicated.
+ GLushort indices[] = {
+ 0, 1, 2, 3, 3, // Face 0 - triangle strip ( v0, v1, v2, v3)
+ 4, 4, 5, 6, 7, 7, // Face 1 - triangle strip ( v4, v5, v6, v7)
+ 8, 8, 9, 10, 11, 11, // Face 2 - triangle strip ( v8, v9, v10, v11)
+ 12, 12, 13, 14, 15, 15, // Face 3 - triangle strip (v12, v13, v14, v15)
+ 16, 16, 17, 18, 19, 19, // Face 4 - triangle strip (v16, v17, v18, v19)
+ 20, 20, 21, 22, 23 // Face 5 - triangle strip (v20, v21, v22, v23)
+ };
+
+ // Transfer vertex data to VBO 0
+ arrayBuf.bind();
+ arrayBuf.allocate(vertices, 24 * sizeof(VertexData));
+
+ // Transfer index data to VBO 1
+ indexBuf.bind();
+ indexBuf.allocate(indices, 34 * sizeof(GLushort));
+}
+
+void GeometryEngine::drawCubeGeometry(QOpenGLShaderProgram *program)
+{
+ // Tell OpenGL which VBOs to use
+ arrayBuf.bind();
+ indexBuf.bind();
+
+ // Offset for position
+ quintptr offset = 0;
+
+ // Tell OpenGL programmable pipeline how to locate vertex position data
+ int vertexLocation = program->attributeLocation("a_position");
+ program->enableAttributeArray(vertexLocation);
+ program->setAttributeBuffer(vertexLocation, GL_FLOAT, offset, 3, sizeof(VertexData));
+
+ // Offset for texture coordinate
+ offset += sizeof(QVector3D);
+
+ // Tell OpenGL programmable pipeline how to locate vertex texture coordinate data
+ int texcoordLocation = program->attributeLocation("a_texcoord");
+ program->enableAttributeArray(texcoordLocation);
+ program->setAttributeBuffer(texcoordLocation, GL_FLOAT, offset, 2, sizeof(VertexData));
+
+ // Draw cube geometry using indices from VBO 1
+ glDrawElements(GL_TRIANGLE_STRIP, 34, GL_UNSIGNED_SHORT, nullptr);
+}
diff --git a/tests/manual/qopenglwidget/dockedopenglwidget/geometryengine.h b/tests/manual/qopenglwidget/dockedopenglwidget/geometryengine.h
new file mode 100644
index 0000000000..ac89e57dda
--- /dev/null
+++ b/tests/manual/qopenglwidget/dockedopenglwidget/geometryengine.h
@@ -0,0 +1,51 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef GEOMETRYENGINE_H
+#define GEOMETRYENGINE_H
+
+#include <QOpenGLFunctions>
+#include <QOpenGLShaderProgram>
+#include <QOpenGLBuffer>
+
+class GeometryEngine : protected QOpenGLFunctions
+{
+public:
+ GeometryEngine();
+ virtual ~GeometryEngine();
+
+ void drawCubeGeometry(QOpenGLShaderProgram *program);
+
+private:
+ void initCubeGeometry();
+
+ QOpenGLBuffer arrayBuf;
+ QOpenGLBuffer indexBuf;
+};
+
+#endif // GEOMETRYENGINE_H
diff --git a/tests/manual/qopenglwidget/dockedopenglwidget/main.cpp b/tests/manual/qopenglwidget/dockedopenglwidget/main.cpp
new file mode 100644
index 0000000000..0c3d064ebe
--- /dev/null
+++ b/tests/manual/qopenglwidget/dockedopenglwidget/main.cpp
@@ -0,0 +1,39 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "mainwindow.h"
+#include <QApplication>
+
+int main(int argc, char *argv[])
+{
+ QApplication a(argc, argv);
+
+ MainWindow w;
+ w.show();
+ return a.exec();
+}
diff --git a/tests/manual/qopenglwidget/dockedopenglwidget/mainwidget.cpp b/tests/manual/qopenglwidget/dockedopenglwidget/mainwidget.cpp
new file mode 100644
index 0000000000..90c47f9202
--- /dev/null
+++ b/tests/manual/qopenglwidget/dockedopenglwidget/mainwidget.cpp
@@ -0,0 +1,186 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "mainwidget.h"
+#include <QMouseEvent>
+#include <cmath>
+
+MainWidget::~MainWidget()
+{
+ cleanup();
+}
+
+void MainWidget::cleanup()
+{
+ makeCurrent();
+ delete texture;
+ texture = nullptr;
+ delete geometries;
+ geometries = nullptr;
+ delete program;
+ program = nullptr;
+ doneCurrent();
+
+ QObject::disconnect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &MainWidget::cleanup);
+}
+
+void MainWidget::mousePressEvent(QMouseEvent *e)
+{
+ // Save mouse press position
+ mousePressPosition = QVector2D(e->position());
+}
+
+void MainWidget::mouseReleaseEvent(QMouseEvent *e)
+{
+ // Mouse release position - mouse press position
+ QVector2D diff = QVector2D(e->position()) - mousePressPosition;
+
+ // Rotation axis is perpendicular to the mouse position difference
+ // vector
+ QVector3D n = QVector3D(diff.y(), diff.x(), 0.0).normalized();
+
+ // Accelerate angular speed relative to the length of the mouse sweep
+ qreal acc = diff.length() / 100.0;
+
+ // Calculate new rotation axis as weighted sum
+ rotationAxis = (rotationAxis * angularSpeed + n * acc).normalized();
+
+ // Increase angular speed
+ angularSpeed += acc;
+}
+
+void MainWidget::timerEvent(QTimerEvent *)
+{
+ // Decrease angular speed (friction)
+ angularSpeed *= 0.99;
+
+ // Stop rotation when speed goes below threshold
+ if (angularSpeed < 0.01) {
+ angularSpeed = 0.0;
+ } else {
+ // Update rotation
+ rotation = QQuaternion::fromAxisAndAngle(rotationAxis, angularSpeed) * rotation;
+
+ // Request an update
+ update();
+ }
+}
+
+void MainWidget::initializeGL()
+{
+ initializeOpenGLFunctions();
+
+ glClearColor(0, 0, 0, 1);
+
+ initShaders();
+ initTextures();
+
+ glEnable(GL_DEPTH_TEST);
+
+ glEnable(GL_CULL_FACE);
+
+ geometries = new GeometryEngine;
+
+ // Use QBasicTimer because its faster than QTimer
+ timer.start(12, this);
+
+ connect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &MainWidget::cleanup);
+}
+
+void MainWidget::initShaders()
+{
+ program = new QOpenGLShaderProgram;
+ // Compile vertex shader
+ if (!program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/vshader.glsl"))
+ close();
+
+ // Compile fragment shader
+ if (!program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/fshader.glsl"))
+ close();
+
+ // Link shader pipeline
+ if (!program->link())
+ close();
+
+ // Bind shader pipeline for use
+ if (!program->bind())
+ close();
+}
+
+void MainWidget::initTextures()
+{
+ // Load cube.png image
+ texture = new QOpenGLTexture(QImage(":/cube.png").mirrored());
+
+ // Set nearest filtering mode for texture minification
+ texture->setMinificationFilter(QOpenGLTexture::Nearest);
+
+ // Set bilinear filtering mode for texture magnification
+ texture->setMagnificationFilter(QOpenGLTexture::Linear);
+
+ // Wrap texture coordinates by repeating
+ // f.ex. texture coordinate (1.1, 1.2) is same as (0.1, 0.2)
+ texture->setWrapMode(QOpenGLTexture::Repeat);
+}
+
+void MainWidget::resizeGL(int w, int h)
+{
+ // Calculate aspect ratio
+ qreal aspect = qreal(w) / qreal(h ? h : 1);
+
+ // Set near plane to 3.0, far plane to 7.0, field of view 45 degrees
+ const qreal zNear = 3.0, zFar = 7.0, fov = 45.0;
+
+ // Reset projection
+ projection.setToIdentity();
+
+ // Set perspective projection
+ projection.perspective(fov, aspect, zNear, zFar);
+}
+
+void MainWidget::paintGL()
+{
+ // Clear color and depth buffer
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ texture->bind();
+
+ // Calculate model view transformation
+ QMatrix4x4 matrix;
+ matrix.translate(0.0, 0.0, -5.0);
+ matrix.rotate(rotation);
+
+ // Set modelview-projection matrix
+ program->setUniformValue("mvp_matrix", projection * matrix);
+
+ // Use texture unit 0 which contains cube.png
+ program->setUniformValue("texture", 0);
+
+ // Draw cube geometry
+ geometries->drawCubeGeometry(program);
+}
diff --git a/tests/manual/qopenglwidget/dockedopenglwidget/mainwidget.h b/tests/manual/qopenglwidget/dockedopenglwidget/mainwidget.h
new file mode 100644
index 0000000000..2b1185c8b7
--- /dev/null
+++ b/tests/manual/qopenglwidget/dockedopenglwidget/mainwidget.h
@@ -0,0 +1,82 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef MAINWIDGET_H
+#define MAINWIDGET_H
+
+#include "geometryengine.h"
+
+#include <QOpenGLWidget>
+#include <QOpenGLFunctions>
+#include <QMatrix4x4>
+#include <QQuaternion>
+#include <QVector2D>
+#include <QBasicTimer>
+#include <QOpenGLShaderProgram>
+#include <QOpenGLTexture>
+
+class GeometryEngine;
+
+class MainWidget : public QOpenGLWidget, protected QOpenGLFunctions
+{
+ Q_OBJECT
+
+public:
+ using QOpenGLWidget::QOpenGLWidget;
+ ~MainWidget();
+
+protected:
+ void mousePressEvent(QMouseEvent *e) override;
+ void mouseReleaseEvent(QMouseEvent *e) override;
+ void timerEvent(QTimerEvent *e) override;
+
+ void initializeGL() override;
+ void resizeGL(int w, int h) override;
+ void paintGL() override;
+
+ void initShaders();
+ void initTextures();
+
+private slots:
+ void cleanup();
+
+private:
+ QBasicTimer timer;
+ QOpenGLShaderProgram *program = nullptr;
+ GeometryEngine *geometries = nullptr;
+ QOpenGLTexture *texture = nullptr;
+
+ QMatrix4x4 projection;
+
+ QVector2D mousePressPosition;
+ QVector3D rotationAxis;
+ qreal angularSpeed = 0;
+ QQuaternion rotation;
+};
+
+#endif // MAINWIDGET_H
diff --git a/tests/manual/qopenglwidget/dockedopenglwidget/mainwindow.cpp b/tests/manual/qopenglwidget/dockedopenglwidget/mainwindow.cpp
new file mode 100644
index 0000000000..d683076c72
--- /dev/null
+++ b/tests/manual/qopenglwidget/dockedopenglwidget/mainwindow.cpp
@@ -0,0 +1,56 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+#include "mainwidget.h"
+#include <QDockWidget>
+
+MainWindow::MainWindow(QWidget *parent)
+ : QMainWindow(parent)
+ , ui(new Ui::MainWindow)
+{
+ ui->setupUi(this);
+
+ MainWidget *w1 = new MainWidget();
+ centralWidget()->layout()->addWidget(w1);
+
+ MainWidget *w2 = new MainWidget();
+ QDockWidget *dock = new QDockWidget("OpenGL Dock", this);
+ w2->setFixedSize(300, 300);
+ dock->setWidget(w2);
+ dock->setFixedSize(300, 300);
+
+ addDockWidget(Qt::RightDockWidgetArea, dock);
+ dock->setFloating(false);
+}
+
+MainWindow::~MainWindow()
+{
+ delete ui;
+}
diff --git a/tests/manual/qopenglwidget/dockedopenglwidget/mainwindow.h b/tests/manual/qopenglwidget/dockedopenglwidget/mainwindow.h
new file mode 100644
index 0000000000..6f51fc172d
--- /dev/null
+++ b/tests/manual/qopenglwidget/dockedopenglwidget/mainwindow.h
@@ -0,0 +1,49 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+
+QT_BEGIN_NAMESPACE
+namespace Ui { class MainWindow; }
+QT_END_NAMESPACE
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ MainWindow(QWidget *parent = nullptr);
+ ~MainWindow();
+
+private:
+ Ui::MainWindow *ui;
+};
+#endif // MAINWINDOW_H
diff --git a/tests/manual/qopenglwidget/dockedopenglwidget/mainwindow.ui b/tests/manual/qopenglwidget/dockedopenglwidget/mainwindow.ui
new file mode 100644
index 0000000000..f013b8ec25
--- /dev/null
+++ b/tests/manual/qopenglwidget/dockedopenglwidget/mainwindow.ui
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>600</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>MainWindow</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QHBoxLayout" name="horizontalLayout"/>
+ </widget>
+ <widget class="QMenuBar" name="menubar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>20</height>
+ </rect>
+ </property>
+ </widget>
+ <widget class="QStatusBar" name="statusbar"/>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/tests/manual/qopenglwidget/dockedopenglwidget/vshader.glsl b/tests/manual/qopenglwidget/dockedopenglwidget/vshader.glsl
new file mode 100644
index 0000000000..098eda946e
--- /dev/null
+++ b/tests/manual/qopenglwidget/dockedopenglwidget/vshader.glsl
@@ -0,0 +1,12 @@
+uniform mat4 mvp_matrix;
+
+attribute vec4 a_position;
+attribute vec2 a_texcoord;
+
+varying vec2 v_texcoord;
+
+void main()
+{
+ gl_Position = mvp_matrix * a_position;
+ v_texcoord = a_texcoord;
+}
diff --git a/tests/manual/qopenglwidget/openglwidget/CMakeLists.txt b/tests/manual/qopenglwidget/openglwidget/CMakeLists.txt
index c0c031489f..50e2fd4d38 100644
--- a/tests/manual/qopenglwidget/openglwidget/CMakeLists.txt
+++ b/tests/manual/qopenglwidget/openglwidget/CMakeLists.txt
@@ -13,6 +13,8 @@ qt_internal_add_manual_test(openglwidget
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
+ Qt::OpenGL
+ Qt::OpenGLWidgets
Qt::Widgets
Qt::WidgetsPrivate
)
diff --git a/tests/manual/qopenglwidget/openglwidget/openglwidget.cpp b/tests/manual/qopenglwidget/openglwidget/openglwidget.cpp
index 031558a787..1bd40f82f7 100644
--- a/tests/manual/qopenglwidget/openglwidget/openglwidget.cpp
+++ b/tests/manual/qopenglwidget/openglwidget/openglwidget.cpp
@@ -39,7 +39,7 @@
#include <QtGui/QOpenGLFunctions>
#include <QtGui/QGuiApplication>
#include <QtGui/QMatrix4x4>
-#include <QtGui/QOpenGLShaderProgram>
+#include <QOpenGLShaderProgram>
#include <QtGui/QScreen>
#include <QtCore/qmath.h>
diff --git a/tests/manual/qopenglwidget/openglwidget/openglwidget.h b/tests/manual/qopenglwidget/openglwidget/openglwidget.h
index 06ec5b1a40..f029d165f9 100644
--- a/tests/manual/qopenglwidget/openglwidget/openglwidget.h
+++ b/tests/manual/qopenglwidget/openglwidget/openglwidget.h
@@ -29,7 +29,7 @@
#ifndef OPENGLWIDGET_H
#define OPENGLWIDGET_H
-#include <QtWidgets/QOpenGLWidget>
+#include <QOpenGLWidget>
#include <QtGui/QVector3D>
class OpenGLWidgetPrivate;
diff --git a/tests/manual/rhi/CMakeLists.txt b/tests/manual/rhi/CMakeLists.txt
index 5441b1e5e8..7fcf783d8f 100644
--- a/tests/manual/rhi/CMakeLists.txt
+++ b/tests/manual/rhi/CMakeLists.txt
@@ -26,3 +26,6 @@ add_subdirectory(texturearray)
add_subdirectory(polygonmode)
add_subdirectory(tessellation)
add_subdirectory(geometryshader)
+if(QT_FEATURE_widgets)
+ add_subdirectory(rhiwidget)
+endif()
diff --git a/tests/manual/rhi/rhiwidget/CMakeLists.txt b/tests/manual/rhi/rhiwidget/CMakeLists.txt
new file mode 100644
index 0000000000..762e6f4103
--- /dev/null
+++ b/tests/manual/rhi/rhiwidget/CMakeLists.txt
@@ -0,0 +1,30 @@
+qt_internal_add_manual_test(rhiwidget
+ GUI
+ SOURCES
+ examplewidget.cpp examplewidget.h
+ rhiwidget.cpp rhiwidget.h rhiwidget_p.h
+ main.cpp
+ PUBLIC_LIBRARIES
+ Qt::Gui
+ Qt::GuiPrivate
+ Qt::Widgets
+ Qt::WidgetsPrivate
+)
+
+set_source_files_properties("../shared/texture.vert.qsb"
+ PROPERTIES QT_RESOURCE_ALIAS "texture.vert.qsb"
+)
+set_source_files_properties("../shared/texture.frag.qsb"
+ PROPERTIES QT_RESOURCE_ALIAS "texture.frag.qsb"
+)
+set(rhiwidget_resource_files
+ "../shared/texture.vert.qsb"
+ "../shared/texture.frag.qsb"
+)
+
+qt_internal_add_resource(rhiwidget "rhiwidget"
+ PREFIX
+ "/"
+ FILES
+ ${rhiwidget_resource_files}
+)
diff --git a/tests/manual/rhi/rhiwidget/examplewidget.cpp b/tests/manual/rhi/rhiwidget/examplewidget.cpp
new file mode 100644
index 0000000000..b46ffdad14
--- /dev/null
+++ b/tests/manual/rhi/rhiwidget/examplewidget.cpp
@@ -0,0 +1,229 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "examplewidget.h"
+#include "../shared/cube.h"
+#include <QFile>
+#include <QPainter>
+
+static const QSize CUBE_TEX_SIZE(512, 512);
+
+ExampleRhiWidget::ExampleRhiWidget(QWidget *parent, Qt::WindowFlags f)
+ : QRhiWidget(parent, f)
+{
+ setDebugLayer(true);
+}
+
+void ExampleRhiWidget::initialize(QRhi *rhi, QRhiTexture *outputTexture)
+{
+ if (m_rhi != rhi) {
+ m_rt.reset();
+ m_rp.reset();
+ m_ds.reset();
+ scene.vbuf.reset();
+ } else if (m_output != outputTexture) {
+ m_rt.reset();
+ m_rp.reset();
+ }
+
+ m_rhi = rhi;
+ m_output = outputTexture;
+
+ if (!m_ds) {
+ m_ds.reset(m_rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, m_output->pixelSize()));
+ m_ds->create();
+ } else if (m_ds->pixelSize() != m_output->pixelSize()) {
+ m_ds->setPixelSize(m_output->pixelSize());
+ m_ds->create();
+ }
+
+ if (!m_rt) {
+ m_rt.reset(m_rhi->newTextureRenderTarget({ { m_output }, m_ds.data() }));
+ m_rp.reset(m_rt->newCompatibleRenderPassDescriptor());
+ m_rt->setRenderPassDescriptor(m_rp.data());
+ m_rt->create();
+ }
+
+ if (!scene.vbuf) {
+ initScene();
+ updateCubeTexture();
+ }
+
+ const QSize outputSize = m_output->pixelSize();
+ scene.mvp = m_rhi->clipSpaceCorrMatrix();
+ scene.mvp.perspective(45.0f, outputSize.width() / (float) outputSize.height(), 0.01f, 1000.0f);
+ scene.mvp.translate(0, 0, -4);
+ updateMvp();
+}
+
+void ExampleRhiWidget::updateMvp()
+{
+ QMatrix4x4 mvp = scene.mvp * QMatrix4x4(QQuaternion::fromEulerAngles(QVector3D(30, itemData.cubeRotation, 0)).toRotationMatrix());
+ if (!scene.resourceUpdates)
+ scene.resourceUpdates = m_rhi->nextResourceUpdateBatch();
+ scene.resourceUpdates->updateDynamicBuffer(scene.ubuf.data(), 0, 64, mvp.constData());
+}
+
+void ExampleRhiWidget::updateCubeTexture()
+{
+ QImage image(CUBE_TEX_SIZE, QImage::Format_RGBA8888);
+ const QRect r(QPoint(0, 0), CUBE_TEX_SIZE);
+ QPainter p(&image);
+ p.fillRect(r, QGradient::DeepBlue);
+ QFont font;
+ font.setPointSize(24);
+ p.setFont(font);
+ p.drawText(r, itemData.cubeText);
+ p.end();
+
+ if (!scene.resourceUpdates)
+ scene.resourceUpdates = m_rhi->nextResourceUpdateBatch();
+ scene.resourceUpdates->uploadTexture(scene.cubeTex.data(), image);
+}
+
+static QShader getShader(const QString &name)
+{
+ QFile f(name);
+ if (f.open(QIODevice::ReadOnly))
+ return QShader::fromSerialized(f.readAll());
+
+ return QShader();
+}
+
+void ExampleRhiWidget::initScene()
+{
+ scene.vbuf.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(cube)));
+ scene.vbuf->create();
+
+ scene.resourceUpdates = m_rhi->nextResourceUpdateBatch();
+ scene.resourceUpdates->uploadStaticBuffer(scene.vbuf.data(), cube);
+
+ scene.ubuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68));
+ scene.ubuf->create();
+
+ const qint32 flip = 0;
+ scene.resourceUpdates->updateDynamicBuffer(scene.ubuf.data(), 64, 4, &flip);
+
+ scene.cubeTex.reset(m_rhi->newTexture(QRhiTexture::RGBA8, CUBE_TEX_SIZE));
+ scene.cubeTex->create();
+
+ scene.sampler.reset(m_rhi->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
+ QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge));
+ scene.sampler->create();
+
+ scene.srb.reset(m_rhi->newShaderResourceBindings());
+ scene.srb->setBindings({
+ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, scene.ubuf.data()),
+ QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, scene.cubeTex.data(), scene.sampler.data())
+ });
+ scene.srb->create();
+
+ scene.ps.reset(m_rhi->newGraphicsPipeline());
+ scene.ps->setDepthTest(true);
+ scene.ps->setDepthWrite(true);
+ scene.ps->setDepthOp(QRhiGraphicsPipeline::Less);
+ scene.ps->setCullMode(QRhiGraphicsPipeline::Back);
+ scene.ps->setFrontFace(QRhiGraphicsPipeline::CCW);
+ QShader vs = getShader(QLatin1String(":/texture.vert.qsb"));
+ Q_ASSERT(vs.isValid());
+ QShader fs = getShader(QLatin1String(":/texture.frag.qsb"));
+ Q_ASSERT(fs.isValid());
+ scene.ps->setShaderStages({
+ { QRhiShaderStage::Vertex, vs },
+ { QRhiShaderStage::Fragment, fs }
+ });
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({
+ { 3 * sizeof(float) },
+ { 2 * sizeof(float) }
+ });
+ inputLayout.setAttributes({
+ { 0, 0, QRhiVertexInputAttribute::Float3, 0 },
+ { 1, 1, QRhiVertexInputAttribute::Float2, 0 }
+ });
+ scene.ps->setVertexInputLayout(inputLayout);
+ scene.ps->setShaderResourceBindings(scene.srb.data());
+ scene.ps->setRenderPassDescriptor(m_rp.data());
+ scene.ps->create();
+}
+
+void ExampleRhiWidget::render(QRhiCommandBuffer *cb)
+{
+ if (itemData.cubeRotationDirty) {
+ itemData.cubeRotationDirty = false;
+ updateMvp();
+ }
+
+ if (itemData.cubeTextDirty) {
+ itemData.cubeTextDirty = false;
+ updateCubeTexture();
+ }
+
+ QRhiResourceUpdateBatch *rub = scene.resourceUpdates;
+ if (rub)
+ scene.resourceUpdates = nullptr;
+
+ const QColor clearColor = QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f);
+
+ cb->beginPass(m_rt.data(), clearColor, { 1.0f, 0 }, rub);
+
+ cb->setGraphicsPipeline(scene.ps.data());
+ const QSize outputSize = m_output->pixelSize();
+ cb->setViewport(QRhiViewport(0, 0, outputSize.width(), outputSize.height()));
+ cb->setShaderResources();
+ const QRhiCommandBuffer::VertexInput vbufBindings[] = {
+ { scene.vbuf.data(), 0 },
+ { scene.vbuf.data(), quint32(36 * 3 * sizeof(float)) }
+ };
+ cb->setVertexInput(0, 2, vbufBindings);
+ cb->draw(36);
+
+ cb->endPass();
+}
diff --git a/tests/manual/rhi/rhiwidget/examplewidget.h b/tests/manual/rhi/rhiwidget/examplewidget.h
new file mode 100644
index 0000000000..64a51cb49b
--- /dev/null
+++ b/tests/manual/rhi/rhiwidget/examplewidget.h
@@ -0,0 +1,113 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef EXAMPLEWIDGET_H
+#define EXAMPLEWIDGET_H
+
+#include "rhiwidget.h"
+#include <QtGui/private/qrhi_p.h>
+
+class ExampleRhiWidget : public QRhiWidget
+{
+public:
+ ExampleRhiWidget(QWidget *parent = nullptr, Qt::WindowFlags f = {});
+
+ void initialize(QRhi *rhi, QRhiTexture *outputTexture) override;
+ void render(QRhiCommandBuffer *cb) override;
+
+ void setCubeTextureText(const QString &s)
+ {
+ if (itemData.cubeText == s)
+ return;
+ itemData.cubeText = s;
+ itemData.cubeTextDirty = true;
+ update();
+ }
+
+ void setCubeRotation(float r)
+ {
+ if (itemData.cubeRotation == r)
+ return;
+ itemData.cubeRotation = r;
+ itemData.cubeRotationDirty = true;
+ update();
+ }
+
+private:
+ QRhi *m_rhi = nullptr;
+ QRhiTexture *m_output = nullptr;
+ QScopedPointer<QRhiRenderBuffer> m_ds;
+ QScopedPointer<QRhiTextureRenderTarget> m_rt;
+ QScopedPointer<QRhiRenderPassDescriptor> m_rp;
+
+ struct {
+ QRhiResourceUpdateBatch *resourceUpdates = nullptr;
+ QScopedPointer<QRhiBuffer> vbuf;
+ QScopedPointer<QRhiBuffer> ubuf;
+ QScopedPointer<QRhiShaderResourceBindings> srb;
+ QScopedPointer<QRhiGraphicsPipeline> ps;
+ QScopedPointer<QRhiSampler> sampler;
+ QScopedPointer<QRhiTexture> cubeTex;
+ QMatrix4x4 mvp;
+ } scene;
+
+ void initScene();
+ void updateMvp();
+ void updateCubeTexture();
+
+ struct {
+ QString cubeText;
+ bool cubeTextDirty = false;
+ float cubeRotation = 0.0f;
+ bool cubeRotationDirty = false;
+ } itemData;
+};
+
+#endif
diff --git a/tests/manual/rhi/rhiwidget/main.cpp b/tests/manual/rhi/rhiwidget/main.cpp
new file mode 100644
index 0000000000..76736d49da
--- /dev/null
+++ b/tests/manual/rhi/rhiwidget/main.cpp
@@ -0,0 +1,144 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QApplication>
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+#include <QSlider>
+#include <QLineEdit>
+#include <QPushButton>
+#include <QLabel>
+#include <QCheckBox>
+#include <QFileDialog>
+#include "examplewidget.h"
+
+static const bool TEST_OFFSCREEN_GRAB = false;
+
+int main(int argc, char **argv)
+{
+ qputenv("QSG_INFO", "1");
+ QApplication app(argc, argv);
+
+ QVBoxLayout *layout = new QVBoxLayout;
+
+ QLineEdit *edit = new QLineEdit(QLatin1String("Text on cube"));
+ QSlider *slider = new QSlider(Qt::Horizontal);
+ ExampleRhiWidget *rw = new ExampleRhiWidget;
+
+ QObject::connect(edit, &QLineEdit::textChanged, edit, [edit, rw] {
+ rw->setCubeTextureText(edit->text());
+ });
+
+ slider->setMinimum(0);
+ slider->setMaximum(360);
+ QObject::connect(slider, &QSlider::valueChanged, slider, [slider, rw] {
+ rw->setCubeRotation(slider->value());
+ });
+
+ QPushButton *btn = new QPushButton(QLatin1String("Grab to image"));
+ QObject::connect(btn, &QPushButton::clicked, btn, [rw] {
+ QImage image = rw->grabTexture();
+ qDebug() << image;
+ if (!image.isNull()) {
+ QFileDialog fd(rw->parentWidget());
+ fd.setAcceptMode(QFileDialog::AcceptSave);
+ fd.setDefaultSuffix("png");
+ fd.selectFile("test.png");
+ if (fd.exec() == QDialog::Accepted)
+ image.save(fd.selectedFiles().first());
+ }
+ });
+ QHBoxLayout *btnLayout = new QHBoxLayout;
+ btnLayout->addWidget(btn);
+ QCheckBox *cbExplicitSize = new QCheckBox(QLatin1String("Use explicit size"));
+ QObject::connect(cbExplicitSize, &QCheckBox::stateChanged, cbExplicitSize, [cbExplicitSize, rw] {
+ if (cbExplicitSize->isChecked())
+ rw->setExplicitSize(QSize(128, 128));
+ else
+ rw->setExplicitSize(QSize());
+ });
+ btnLayout->addWidget(cbExplicitSize);
+ QPushButton *btnMakeWindow = new QPushButton(QLatin1String("Make top-level window"));
+ QObject::connect(btnMakeWindow, &QPushButton::clicked, btnMakeWindow, [rw, btnMakeWindow, layout] {
+ if (rw->parentWidget()) {
+ rw->setParent(nullptr);
+ rw->setAttribute(Qt::WA_DeleteOnClose, true);
+ rw->show();
+ btnMakeWindow->setText(QLatin1String("Make child widget"));
+ } else {
+ rw->setAttribute(Qt::WA_DeleteOnClose, false);
+ layout->addWidget(rw);
+ btnMakeWindow->setText(QLatin1String("Make top-level window"));
+ }
+ });
+ btnLayout->addWidget(btnMakeWindow);
+
+ layout->addWidget(edit);
+ QHBoxLayout *sliderLayout = new QHBoxLayout;
+ sliderLayout->addWidget(new QLabel(QLatin1String("Cube rotation")));
+ sliderLayout->addWidget(slider);
+ layout->addLayout(sliderLayout);
+ layout->addLayout(btnLayout);
+ layout->addWidget(rw);
+
+ rw->setCubeTextureText(edit->text());
+
+ if (TEST_OFFSCREEN_GRAB) {
+ rw->resize(320, 200);
+ rw->grabTexture().save("offscreen_grab.png");
+ }
+
+ QWidget w;
+ w.setLayout(layout);
+ w.resize(1280, 720);
+ w.show();
+
+ return app.exec();
+}
diff --git a/tests/manual/rhi/rhiwidget/rhiwidget.cpp b/tests/manual/rhi/rhiwidget/rhiwidget.cpp
new file mode 100644
index 0000000000..289e69cdff
--- /dev/null
+++ b/tests/manual/rhi/rhiwidget/rhiwidget.cpp
@@ -0,0 +1,592 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "rhiwidget_p.h"
+
+#include <private/qguiapplication_p.h>
+#include <qpa/qplatformintegration.h>
+#include <private/qwidgetrepaintmanager_p.h>
+
+/*!
+ \class QRhiWidget
+ \inmodule QtWidgets
+ \since 6.x
+
+ \brief The QRhiWidget class is a widget for rendering 3D graphics via an
+ accelerated grapics API, such as Vulkan, Metal, or Direct 3D.
+
+ QRhiWidget provides functionality for displaying 3D content rendered through
+ the QRhi APIs within a QWidget-based application.
+
+ QRhiWidget is expected to be subclassed. To render into the 2D texture that
+ is implicitly created and managed by the QRhiWidget, subclasses should
+ reimplement the virtual functions initialize() and render().
+
+ The size of the texture will by default adapt to the size of the item. If a
+ fixed size is preferred, set an explicit size specified in pixels by
+ calling setExplicitSize().
+
+ The QRhi for the widget's top-level window is configured to use a platform
+ specific backend and graphics API by default: Metal on macOS and iOS,
+ Direct 3D 11 on Windows, OpenGL otherwise. Call setApi() to override this.
+
+ \note A single widget window can only use one QRhi backend, and so graphics
+ API. If two QRhiWidget or QQuickWidget widgets in the window's widget
+ hierarchy request different APIs, only one of them will function correctly.
+ */
+
+/*!
+ Constructs a widget which is a child of \a parent, with widget flags set to \a f.
+ */
+QRhiWidget::QRhiWidget(QWidget *parent, Qt::WindowFlags f)
+ : QWidget(*(new QRhiWidgetPrivate), parent, f)
+{
+ Q_D(QRhiWidget);
+ if (Q_UNLIKELY(!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::RhiBasedRendering)))
+ qWarning("QRhiWidget: QRhi is not supported on this platform.");
+ else
+ d->setRenderToTexture();
+
+ d->config.setEnabled(true);
+#if defined(Q_OS_DARWIN)
+ d->config.setApi(QPlatformBackingStoreRhiConfig::Metal);
+#elif defined(Q_OS_WIN)
+ d->config.setApi(QPlatformBackingStoreRhiConfig::D3D11);
+#else
+ d->config.setApi(QPlatformBackingStoreRhiConfig::OpenGL);
+#endif
+}
+
+/*!
+ Destructor.
+ */
+QRhiWidget::~QRhiWidget()
+{
+ Q_D(QRhiWidget);
+ // rhi resources must be destroyed here, cannot be left to the private dtor
+ delete d->t;
+ d->offscreenRenderer.reset();
+}
+
+/*!
+ Handles resize events that are passed in the \a e event parameter. Calls
+ the virtual function initialize().
+
+ \note Avoid overriding this function in derived classes. If that is not
+ feasible, make sure that QRhiWidget's implementation is invoked too.
+ Otherwise the underlying texture object and related resources will not get
+ resized properly and will lead to incorrect rendering.
+ */
+void QRhiWidget::resizeEvent(QResizeEvent *e)
+{
+ Q_D(QRhiWidget);
+
+ if (e->size().isEmpty()) {
+ d->noSize = true;
+ return;
+ }
+ d->noSize = false;
+
+ d->sendPaintEvent(QRect(QPoint(0, 0), size()));
+}
+
+/*!
+ Handles paint events.
+
+ Calling QWidget::update() will lead to sending a paint event \a e, and thus
+ invoking this function. (NB this is asynchronous and will happen at some
+ point after returning from update()). This function will then, after some
+ preparation, call the virtual render() to update the contents of the
+ QRhiWidget's associated texture. The widget's top-level window will then
+ composite the texture with the rest of the window.
+ */
+void QRhiWidget::paintEvent(QPaintEvent *)
+{
+ Q_D(QRhiWidget);
+ if (!updatesEnabled() || d->noSize)
+ return;
+
+ d->ensureRhi();
+ if (!d->rhi) {
+ qWarning("QRhiWidget: No QRhi");
+ return;
+ }
+
+ const QSize prevSize = d->t ? d->t->pixelSize() : QSize();
+ d->ensureTexture();
+ if (!d->t)
+ return;
+ if (d->t->pixelSize() != prevSize)
+ initialize(d->rhi, d->t);
+
+ QRhiCommandBuffer *cb = nullptr;
+ d->rhi->beginOffscreenFrame(&cb);
+ render(cb);
+ d->rhi->endOffscreenFrame();
+}
+
+/*!
+ \reimp
+*/
+bool QRhiWidget::event(QEvent *e)
+{
+ Q_D(QRhiWidget);
+ switch (e->type()) {
+ case QEvent::WindowChangeInternal:
+ // the QRhi will almost certainly change, prevent texture() from
+ // returning the existing QRhiTexture in the meantime
+ d->textureInvalid = true;
+ break;
+ case QEvent::Show:
+ if (isVisible())
+ d->sendPaintEvent(QRect(QPoint(0, 0), size()));
+ break;
+ default:
+ break;
+ }
+ return QWidget::event(e);
+}
+
+QPlatformBackingStoreRhiConfig QRhiWidgetPrivate::rhiConfig() const
+{
+ return config;
+}
+
+void QRhiWidgetPrivate::ensureRhi()
+{
+ Q_Q(QRhiWidget);
+ // the QRhi and infrastructure belongs to the top-level widget, not to this widget
+ QWidget *tlw = q->window();
+ QWidgetPrivate *wd = get(tlw);
+
+ QRhi *currentRhi = nullptr;
+ if (QWidgetRepaintManager *repaintManager = wd->maybeRepaintManager())
+ currentRhi = repaintManager->rhi();
+
+ if (currentRhi && currentRhi->backend() != QBackingStoreRhiSupport::apiToRhiBackend(config.api())) {
+ qWarning("The top-level window is already using another graphics API for composition, "
+ "'%s' is not compatible with this widget",
+ currentRhi->backendName());
+ return;
+ }
+
+ if (currentRhi && rhi && rhi != currentRhi) {
+ // the texture belongs to the old rhi, drop it, this will also lead to
+ // initialize() being called again
+ delete t;
+ t = nullptr;
+ // if previously we created our own but now get a QRhi from the
+ // top-level, then drop what we have and start using the top-level's
+ if (rhi == offscreenRenderer.rhi())
+ offscreenRenderer.reset();
+ }
+
+ rhi = currentRhi;
+}
+
+void QRhiWidgetPrivate::ensureTexture()
+{
+ Q_Q(QRhiWidget);
+
+ QSize newSize = explicitSize;
+ if (newSize.isEmpty())
+ newSize = q->size() * q->devicePixelRatio();
+
+ if (!t) {
+ if (!rhi->isTextureFormatSupported(format))
+ qWarning("QRhiWidget: The requested texture format is not supported by the graphics API implementation");
+ t = rhi->newTexture(format, newSize, 1, QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
+ if (!t->create()) {
+ qWarning("Failed to create backing texture for QRhiWidget");
+ delete t;
+ t = nullptr;
+ return;
+ }
+ }
+
+ if (t->pixelSize() != newSize) {
+ t->setPixelSize(newSize);
+ if (!t->create())
+ qWarning("Failed to rebuild texture for QRhiWidget after resizing");
+ }
+
+ textureInvalid = false;
+}
+
+/*!
+ \return the currently set graphics API (QRhi backend).
+
+ \sa setApi()
+ */
+QRhiWidget::Api QRhiWidget::api() const
+{
+ Q_D(const QRhiWidget);
+ switch (d->config.api()) {
+ case QPlatformBackingStoreRhiConfig::OpenGL:
+ return OpenGL;
+ case QPlatformBackingStoreRhiConfig::Metal:
+ return Metal;
+ case QPlatformBackingStoreRhiConfig::Vulkan:
+ return Vulkan;
+ case QPlatformBackingStoreRhiConfig::D3D11:
+ return D3D11;
+ default:
+ return Null;
+ }
+}
+
+/*!
+ Sets the graphics API and QRhi backend to use to \a api.
+
+ The default value depends on the platform: Metal on macOS and iOS, Direct
+ 3D 11 on Windows, OpenGL otherwise.
+
+ \note This function must be called early enough, before the widget is added
+ to a widget hierarchy and displayed on screen. For example, aim to call the
+ function for the subclass constructor. If called too late, the function
+ will have no effect.
+
+ The \a api can only be set once for the widget and its top-level window,
+ once it is done and takes effect, the window can only use that API and QRhi
+ backend to render. Attempting to set another value, or to add another
+ QRhiWidget with a different \a api will not function as expected.
+
+ \sa setTextureFormat(), setDebugLayer(), api()
+ */
+void QRhiWidget::setApi(Api api)
+{
+ Q_D(QRhiWidget);
+ switch (api) {
+ case OpenGL:
+ d->config.setApi(QPlatformBackingStoreRhiConfig::OpenGL);
+ break;
+ case Metal:
+ d->config.setApi(QPlatformBackingStoreRhiConfig::Metal);
+ break;
+ case Vulkan:
+ d->config.setApi(QPlatformBackingStoreRhiConfig::Vulkan);
+ break;
+ case D3D11:
+ d->config.setApi(QPlatformBackingStoreRhiConfig::D3D11);
+ break;
+ default:
+ d->config.setApi(QPlatformBackingStoreRhiConfig::Null);
+ break;
+ }
+}
+
+/*!
+ \return true if a debug or validation layer will be requested if applicable
+ to the graphics API in use.
+
+ \sa setDebugLayer()
+ */
+bool QRhiWidget::isDebugLayerEnabled() const
+{
+ Q_D(const QRhiWidget);
+ return d->config.isDebugLayerEnabled();
+}
+
+/*!
+ Requests the debug or validation layer of the underlying graphics API
+ when \a enable is true.
+
+ Applicable for Vulkan and Direct 3D.
+
+ \note This function must be called early enough, before the widget is added
+ to a widget hierarchy and displayed on screen. For example, aim to call the
+ function for the subclass constructor. If called too late, the function
+ will have no effect.
+
+ By default this is disabled.
+
+ \sa setApi(), isDebugLayerEnabled()
+ */
+void QRhiWidget::setDebugLayer(bool enable)
+{
+ Q_D(QRhiWidget);
+ d->config.setDebugLayer(enable);
+}
+
+/*!
+ \return the currently set texture format.
+
+ The default value is QRhiTexture::RGBA8.
+
+ \sa setTextureFormat()
+ */
+QRhiTexture::Format QRhiWidget::textureFormat() const
+{
+ Q_D(const QRhiWidget);
+ return d->format;
+}
+
+/*!
+ Sets the associated texture's \a format.
+
+ The default value is QRhiTexture::RGBA8. Only formats that are reported as
+ supported from QRhi::isTextureFormatSupported() should be specified,
+ rendering will not be functional otherwise.
+
+ \note This function must be called early enough, before the widget is added
+ to a widget hierarchy and displayed on screen. For example, aim to call the
+ function for the subclass constructor. If called too late, the function
+ will have no effect.
+
+ \sa setApi(), textureFormat()
+ */
+void QRhiWidget::setTextureFormat(QRhiTexture::Format format)
+{
+ Q_D(QRhiWidget);
+ d->format = format;
+}
+
+/*!
+ \property QRhiWidget::explicitSize
+
+ The fixed size (in pixels) of the QRhiWidget's associated texture.
+
+ Only relevant when a fixed texture size is desired that does not depend on
+ the widget's size.
+
+ By default the value is a null QSize. A null or empty QSize means that the
+ texture's size follows the QRhiWidget's size. (\c{texture size} = \c{widget
+ size} * \c{device pixel ratio}).
+ */
+
+QSize QRhiWidget::explicitSize() const
+{
+ Q_D(const QRhiWidget);
+ return d->explicitSize;
+}
+
+void QRhiWidget::setExplicitSize(const QSize &pixelSize)
+{
+ Q_D(QRhiWidget);
+ if (d->explicitSize != pixelSize) {
+ d->explicitSize = pixelSize;
+ emit explicitSizeChanged(pixelSize);
+ update();
+ }
+}
+
+/*!
+ Renders a new frame, reads the contents of the texture back, and returns it
+ as a QImage.
+
+ When an error occurs, a null QImage is returned.
+
+ \note This function only supports reading back QRhiTexture::RGBA8 textures
+ at the moment. For other formats, the implementer of render() should
+ implement their own readback logic as they see fit.
+
+ The returned QImage will have a format of QImage::Format_RGBA8888.
+ QRhiWidget does not know the renderer's approach to blending and
+ composition, and therefore cannot know if the output has alpha
+ premultiplied.
+
+ This function can also be called when the QRhiWidget is not added to a
+ widget hierarchy belonging to an on-screen top-level window. This allows
+ generating an image from a 3D rendering off-screen.
+
+ \sa setTextureFormat()
+ */
+QImage QRhiWidget::grabTexture()
+{
+ Q_D(QRhiWidget);
+ if (d->noSize)
+ return QImage();
+
+ if (d->format != QRhiTexture::RGBA8) {
+ qWarning("QRhiWidget::grabTexture() only supports RGBA8 textures");
+ return QImage();
+ }
+
+ d->ensureRhi();
+ if (!d->rhi) {
+ // The widget (and its parent chain, if any) may not be shown at
+ // all, yet one may still want to use it for grabs. This is
+ // ridiculous of course because the rendering infrastructure is
+ // tied to the top-level widget that initializes upon expose, but
+ // it has to be supported.
+ d->offscreenRenderer.setConfig(d->config);
+ // no window passed in, so no swapchain, but we get a functional QRhi which we own
+ d->offscreenRenderer.create();
+ d->rhi = d->offscreenRenderer.rhi();
+ if (!d->rhi) {
+ qWarning("QRhiWidget: Failed to create dedicated QRhi for grabbing");
+ return QImage();
+ }
+ }
+
+ const QSize prevSize = d->t ? d->t->pixelSize() : QSize();
+ d->ensureTexture();
+ if (!d->t)
+ return QImage();
+ if (d->t->pixelSize() != prevSize)
+ initialize(d->rhi, d->t);
+
+ QRhiReadbackResult readResult;
+ bool readCompleted = false;
+ readResult.completed = [&readCompleted] { readCompleted = true; };
+
+ QRhiCommandBuffer *cb = nullptr;
+ d->rhi->beginOffscreenFrame(&cb);
+ render(cb);
+ QRhiResourceUpdateBatch *readbackBatch = d->rhi->nextResourceUpdateBatch();
+ readbackBatch->readBackTexture(d->t, &readResult);
+ cb->resourceUpdate(readbackBatch);
+ d->rhi->endOffscreenFrame();
+
+ if (readCompleted) {
+ QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
+ readResult.pixelSize.width(), readResult.pixelSize.height(),
+ QImage::Format_RGBA8888);
+ QImage result = wrapperImage.copy();
+ result.setDevicePixelRatio(devicePixelRatio());
+ return result;
+ } else {
+ Q_UNREACHABLE();
+ }
+
+ return QImage();
+}
+
+/*!
+ Called when the widget is initialized, when the associated texture's size
+ changes, or when the QRhi and texture change for some reason.
+
+ The implementation should be prepared that both \a rhi and \a outputTexture
+ can change between invocations of this function, although this is not
+ always going to happen in practice. When the widget size changes, this
+ function is called with the same \a rhi and \a outputTexture as before, but
+ \a outputTexture may have been rebuilt, meaning its
+ \l{QRhiTexture::pixelSize()}{size} and the underlying native texture
+ resource may be different than in the last invocation.
+
+ One special case where the objects will be different is when performing a
+ grabTexture() with a widget that is not yet shown, and then making the
+ widget visible on-screen within a top-level widget. There the grab will
+ happen with a dedicated QRhi that is then replaced with the top-level
+ window's associated QRhi in subsequent initialize() and render()
+ invocations.
+
+ Another, more common case is when the widget is reparented so that it
+ belongs to a new top-level window. In this case \a rhi and \a outputTexture
+ will definitely be different in the subsequent call to this function. Is is
+ then important that all existing QRhi resources are destroyed because they
+ belong to the previous QRhi that should not be used by the widget anymore.
+
+ Implementations will typically create or rebuild a QRhiTextureRenderTarget
+ in order to allow the subsequent render() call to render into the texture.
+ When a depth buffer is necessary create a QRhiRenderBuffer as well. The
+ size if this must follow the size of \a outputTexture. A compact and
+ efficient way for this is the following:
+
+ \code
+ if (m_rhi != rhi) {
+ // reset all resources (incl. m_ds, m_rt, m_rp)
+ } else if (m_output != outputTexture) {
+ // reset m_rt and m_rp
+ }
+ m_rhi = rhi;
+ m_output = outputTexture;
+ if (!m_ds) {
+ // no depth-stencil buffer yet, create one
+ m_ds = m_rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, m_output->pixelSize());
+ m_ds->create();
+ } else if (m_ds->pixelSize() != m_output->pixelSize()) {
+ // the size has changed, update the size and rebuild
+ m_ds->setPixelSize(m_output->pixelSize());
+ m_ds->create();
+ }
+ if (!m_rt) {
+ m_rt = m_rhi->newTextureRenderTarget({ { m_output }, m_ds });
+ m_rp = m_rt->newCompatibleRenderPassDescriptor();
+ m_rt->setRenderPassDescriptor(m_rp);
+ m_rt->create();
+ }
+ \endcode
+
+ The above snippet is also prepared for \a rhi and \a outputTexture changing
+ between invocations, via the checks at the beginning of the function.
+
+ The created resources are expected to be released in the destructor
+ implementation of the subclass. \a rhi and \a outputTexture are not owned
+ by, and are guaranteed to outlive the QRhiWidget.
+
+ \sa render()
+ */
+void QRhiWidget::initialize(QRhi *rhi, QRhiTexture *outputTexture)
+{
+ Q_UNUSED(rhi);
+ Q_UNUSED(outputTexture);
+}
+
+/*!
+ Called when the widget contents (i.e. the contents of the texture) need
+ updating.
+
+ There is always at least one call to initialize() before this function is
+ called.
+
+ To request updates, call QWidget::update(). Calling update() from within
+ render() will lead to updating continuously, throttled by vsync.
+
+ \a cb is the QRhiCommandBuffer for the current frame of the Qt Quick
+ scenegraph. The function is called with a frame being recorded, but without
+ an active render pass.
+
+ \sa initialize()
+ */
+void QRhiWidget::render(QRhiCommandBuffer *cb)
+{
+ Q_UNUSED(cb);
+}
diff --git a/tests/manual/rhi/rhiwidget/rhiwidget.h b/tests/manual/rhi/rhiwidget/rhiwidget.h
new file mode 100644
index 0000000000..de0a2f4e86
--- /dev/null
+++ b/tests/manual/rhi/rhiwidget/rhiwidget.h
@@ -0,0 +1,103 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef RHIWIDGET_H
+#define RHIWIDGET_H
+
+#include <QWidget>
+#include <QtGui/private/qrhi_p.h>
+
+class QRhiWidgetPrivate;
+
+class QRhiWidget : public QWidget
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(QRhiWidget)
+ Q_PROPERTY(QSize explicitSize READ explicitSize WRITE setExplicitSize NOTIFY explicitSizeChanged)
+
+public:
+ QRhiWidget(QWidget *parent = nullptr, Qt::WindowFlags f = {});
+ ~QRhiWidget();
+
+ enum Api {
+ OpenGL,
+ Metal,
+ Vulkan,
+ D3D11,
+ Null
+ };
+
+ Api api() const;
+ void setApi(Api api);
+
+ bool isDebugLayerEnabled() const;
+ void setDebugLayer(bool enable);
+
+ QRhiTexture::Format textureFormat() const;
+ void setTextureFormat(QRhiTexture::Format format);
+
+ QSize explicitSize() const;
+ void setExplicitSize(const QSize &pixelSize);
+
+ virtual void initialize(QRhi *rhi, QRhiTexture *outputTexture);
+ virtual void render(QRhiCommandBuffer *cb);
+
+ QImage grabTexture();
+
+Q_SIGNALS:
+ void explicitSizeChanged(const QSize &pixelSize);
+
+protected:
+ void resizeEvent(QResizeEvent *e) override;
+ void paintEvent(QPaintEvent *e) override;
+ bool event(QEvent *e) override;
+};
+
+#endif
diff --git a/tests/manual/rhi/rhiwidget/rhiwidget_p.h b/tests/manual/rhi/rhiwidget/rhiwidget_p.h
new file mode 100644
index 0000000000..ebaef2d6c6
--- /dev/null
+++ b/tests/manual/rhi/rhiwidget/rhiwidget_p.h
@@ -0,0 +1,79 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef RHIWIDGET_P_H
+#define RHIWIDGET_P_H
+
+#include "rhiwidget.h"
+
+#include <private/qwidget_p.h>
+#include <private/qbackingstorerhisupport_p.h>
+
+class QRhiWidgetPrivate : public QWidgetPrivate
+{
+ Q_DECLARE_PUBLIC(QRhiWidget)
+public:
+ QRhiTexture *texture() const override { return textureInvalid ? nullptr : t; }
+ QPlatformBackingStoreRhiConfig rhiConfig() const override;
+
+ void ensureRhi();
+ void ensureTexture();
+
+ QRhi *rhi = nullptr;
+ QRhiTexture *t = nullptr;
+ bool noSize = false;
+ QPlatformBackingStoreRhiConfig config;
+ QRhiTexture::Format format = QRhiTexture::RGBA8;
+ QSize explicitSize;
+ QBackingStoreRhiSupport offscreenRenderer;
+ bool textureInvalid = false;
+};
+
+#endif