summaryrefslogtreecommitdiffstats
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
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>
-rw-r--r--examples/opengl/hellogl2/glwidget.cpp1
-rw-r--r--examples/opengl/qopenglwidget/glwidget.cpp51
-rw-r--r--examples/opengl/qopenglwidget/glwidget.h9
-rw-r--r--examples/opengl/qopenglwidget/main.cpp3
-rw-r--r--examples/opengl/qopenglwidget/mainwindow.cpp56
-rw-r--r--examples/opengl/qopenglwidget/mainwindow.h3
-rw-r--r--src/corelib/kernel/qcoreevent.cpp1
-rw-r--r--src/corelib/kernel/qcoreevent.h4
-rw-r--r--src/gui/CMakeLists.txt11
-rw-r--r--src/gui/kernel/qwindow.cpp7
-rw-r--r--src/gui/kernel/qwindow_p.h4
-rw-r--r--src/gui/painting/qbackingstore.cpp7
-rw-r--r--src/gui/painting/qbackingstoredefaultcompositor.cpp630
-rw-r--r--src/gui/painting/qbackingstoredefaultcompositor_p.h123
-rw-r--r--src/gui/painting/qbackingstorerhisupport.cpp355
-rw-r--r--src/gui/painting/qbackingstorerhisupport_p.h114
-rw-r--r--src/gui/painting/qplatformbackingstore.cpp167
-rw-r--r--src/gui/painting/qplatformbackingstore.h129
-rw-r--r--src/gui/painting/qrhibackingstore.cpp (renamed from src/opengl/qplatformbackingstoreopenglsupport.h)75
-rw-r--r--src/gui/painting/qrhibackingstore_p.h (renamed from src/plugins/platforms/ios/qiosbackingstore.h)27
-rw-r--r--src/gui/painting/shaders/backingstorecompose.frag23
-rw-r--r--src/gui/painting/shaders/backingstorecompose.frag.qsbbin0 -> 1759 bytes
-rw-r--r--src/gui/painting/shaders/backingstorecompose.vert19
-rw-r--r--src/gui/painting/shaders/backingstorecompose.vert.qsbbin0 -> 1627 bytes
-rw-r--r--src/opengl/CMakeLists.txt1
-rw-r--r--src/opengl/doc/snippets/code/doc_gui_widgets_qopenglwidget.cpp8
-rw-r--r--src/opengl/qopenglcompositor.cpp6
-rw-r--r--src/opengl/qopenglcompositorbackingstore.cpp42
-rw-r--r--src/opengl/qopenglcompositorbackingstore_p.h8
-rw-r--r--src/opengl/qplatformbackingstoreopenglsupport.cpp465
-rw-r--r--src/openglwidgets/qopenglwidget.cpp247
-rw-r--r--src/plugins/platforms/cocoa/qcocoabackingstore.h10
-rw-r--r--src/plugins/platforms/cocoa/qcocoabackingstore.mm13
-rw-r--r--src/plugins/platforms/cocoa/qcocoaintegration.mm12
-rw-r--r--src/plugins/platforms/ios/CMakeLists.txt1
-rw-r--r--src/plugins/platforms/ios/qiosbackingstore.mm95
-rw-r--r--src/plugins/platforms/ios/qiosintegration.mm5
-rw-r--r--src/plugins/platforms/ios/qioswindow.mm3
-rw-r--r--src/plugins/platforms/wasm/qwasmbackingstore.cpp2
-rw-r--r--src/plugins/platforms/wasm/qwasmintegration.cpp1
-rw-r--r--src/plugins/platforms/windows/qwindowsgdiintegration.cpp4
-rw-r--r--src/plugins/platforms/xcb/qxcbbackingstore.cpp16
-rw-r--r--src/plugins/platforms/xcb/qxcbbackingstore.h6
-rw-r--r--src/plugins/platforms/xcb/qxcbintegration.cpp3
-rw-r--r--src/widgets/kernel/qwidget.cpp141
-rw-r--r--src/widgets/kernel/qwidget_p.h20
-rw-r--r--src/widgets/kernel/qwidgetrepaintmanager.cpp109
-rw-r--r--src/widgets/kernel/qwidgetrepaintmanager_p.h6
-rw-r--r--src/widgets/kernel/qwidgetwindow.cpp22
-rw-r--r--src/widgets/kernel/qwindowcontainer.cpp9
-rw-r--r--src/widgets/widgets/qwidgetlinecontrol_p.h3
-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
74 files changed, 3982 insertions, 1089 deletions
diff --git a/examples/opengl/hellogl2/glwidget.cpp b/examples/opengl/hellogl2/glwidget.cpp
index 9988902071..23736cd752 100644
--- a/examples/opengl/hellogl2/glwidget.cpp
+++ b/examples/opengl/hellogl2/glwidget.cpp
@@ -131,6 +131,7 @@ void GLWidget::cleanup()
delete m_program;
m_program = nullptr;
doneCurrent();
+ QObject::disconnect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &GLWidget::cleanup);
}
static const char *vertexShaderSourceCore =
diff --git a/examples/opengl/qopenglwidget/glwidget.cpp b/examples/opengl/qopenglwidget/glwidget.cpp
index a480fe5726..da098319e4 100644
--- a/examples/opengl/qopenglwidget/glwidget.cpp
+++ b/examples/opengl/qopenglwidget/glwidget.cpp
@@ -66,9 +66,8 @@ const int bubbleNum = 8;
#define GL_SRGB8_ALPHA8 0x8C43
#endif
-GLWidget::GLWidget(MainWindow *mw, bool button, const QColor &background)
- : m_mainWindow(mw),
- m_hasButton(button),
+GLWidget::GLWidget(MainWindow *maybeMainWindow, const QColor &background)
+ : m_mainWindow(maybeMainWindow),
m_background(background)
{
setMinimumSize(300, 250);
@@ -78,20 +77,41 @@ GLWidget::GLWidget(MainWindow *mw, bool button, const QColor &background)
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)
@@ -334,6 +354,13 @@ void GLWidget::initializeGL()
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()
@@ -403,7 +430,7 @@ void GLWidget::paintGL()
// When requested, follow the ideal way to animate: Rely on
// blocking swap and just schedule updates continuously.
- if (!m_mainWindow->timerEnabled())
+ if (!m_mainWindow || !m_mainWindow->timerEnabled())
update();
}
@@ -545,16 +572,16 @@ void GLWidget::setTransparent(bool transparent)
void GLWidget::resizeGL(int, int)
{
- if (m_hasButton) {
+ if (m_mainWindow) {
if (!m_btn) {
- m_btn = new QPushButton("A widget on top.\nPress for more widgets.", this);
- connect(m_btn, &QPushButton::clicked, this, &GLWidget::handleButtonPress);
+ 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);
}
}
-
-void GLWidget::handleButtonPress()
-{
- m_mainWindow->addNew();
-}
diff --git a/examples/opengl/qopenglwidget/glwidget.h b/examples/opengl/qopenglwidget/glwidget.h
index 50f8aa9be5..bc664f474c 100644
--- a/examples/opengl/qopenglwidget/glwidget.h
+++ b/examples/opengl/qopenglwidget/glwidget.h
@@ -71,7 +71,7 @@ class GLWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
Q_OBJECT
public:
- GLWidget(MainWindow *mw, bool button, const QColor &background);
+ GLWidget(MainWindow *maybeMainWindow, const QColor &background);
~GLWidget();
public slots:
@@ -81,9 +81,6 @@ public slots:
void setShowBubbles(bool);
void setTransparent(bool transparent);
-private slots:
- void handleButtonPress();
-
protected:
void resizeGL(int w, int h) override;
void paintGL() override;
@@ -96,6 +93,7 @@ private:
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;
@@ -126,8 +124,9 @@ private:
int m_textureUniform2 = 0;
bool m_transparent = false;
QPushButton *m_btn = nullptr;
- bool m_hasButton;
+ QPushButton *m_btn2 = nullptr;
QColor m_background;
+ QMetaObject::Connection m_contextWatchConnection;
};
#endif
diff --git a/examples/opengl/qopenglwidget/main.cpp b/examples/opengl/qopenglwidget/main.cpp
index 968b2a7258..08d1f2f14f 100644
--- a/examples/opengl/qopenglwidget/main.cpp
+++ b/examples/opengl/qopenglwidget/main.cpp
@@ -84,6 +84,7 @@ int main( int argc, char ** argv )
QSurfaceFormat::setDefaultFormat(format);
MainWindow mw;
- mw.showMaximized();
+ mw.resize(1280, 720);
+ mw.show();
return a.exec();
}
diff --git a/examples/opengl/qopenglwidget/mainwindow.cpp b/examples/opengl/qopenglwidget/mainwindow.cpp
index eb34f87cb7..b3cbb4a69b 100644
--- a/examples/opengl/qopenglwidget/mainwindow.cpp
+++ b/examples/opengl/qopenglwidget/mainwindow.cpp
@@ -59,13 +59,16 @@
#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, true, qRgb(20, 20, 50));
+ GLWidget *glwidget = new GLWidget(this, qRgb(20, 20, 50));
m_glWidgets << glwidget;
QLabel *label = new QLabel(this);
m_timer = new QTimer(this);
@@ -127,6 +130,7 @@ MainWindow::MainWindow()
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);
@@ -156,9 +160,9 @@ void MainWindow::addNew()
{
if (m_nextY == 4)
return;
- GLWidget *w = new GLWidget(this, false, qRgb(QRandomGenerator::global()->bounded(256),
- QRandomGenerator::global()->bounded(256),
- QRandomGenerator::global()->bounded(256)));
+ 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);
@@ -185,3 +189,47 @@ 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/examples/opengl/qopenglwidget/mainwindow.h b/examples/opengl/qopenglwidget/mainwindow.h
index 43cc55100e..69dcb34bb9 100644
--- a/examples/opengl/qopenglwidget/mainwindow.h
+++ b/examples/opengl/qopenglwidget/mainwindow.h
@@ -68,6 +68,9 @@ public:
void resizeEvent(QResizeEvent *);
+public slots:
+ void showNewWindow();
+
private slots:
void updateIntervalChanged(int value);
void timerUsageChanged(bool enabled);
diff --git a/src/corelib/kernel/qcoreevent.cpp b/src/corelib/kernel/qcoreevent.cpp
index cc804a55c7..ebb601722f 100644
--- a/src/corelib/kernel/qcoreevent.cpp
+++ b/src/corelib/kernel/qcoreevent.cpp
@@ -290,6 +290,7 @@ QT_BEGIN_NAMESPACE
\omitvalue NativeGesture
\omitvalue WindowChangeInternal
\omitvalue ScreenChangeInternal
+ \omitvalue WindowAboutToChangeInternal
*/
/*!
diff --git a/src/corelib/kernel/qcoreevent.h b/src/corelib/kernel/qcoreevent.h
index 42d1925440..4a4910e893 100644
--- a/src/corelib/kernel/qcoreevent.h
+++ b/src/corelib/kernel/qcoreevent.h
@@ -285,7 +285,7 @@ public:
StyleAnimationUpdate = 213, // style animation target should be updated
ApplicationStateChange = 214,
- WindowChangeInternal = 215, // internal for QQuickWidget
+ WindowChangeInternal = 215, // internal for QQuickWidget and texture-based widgets
ScreenChangeInternal = 216,
PlatformSurface = 217, // Platform surface created or about to be destroyed
@@ -294,6 +294,8 @@ public:
TabletTrackingChange = 219, // tablet tracking state has changed
+ WindowAboutToChangeInternal = 220, // internal for QQuickWidget and texture-based widgets
+
// 512 reserved for Qt Jambi's MetaCall event
// 513 reserved for Qt Jambi's DeleteOnMainThread event
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 9e045be4aa..c37bf31348 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -145,6 +145,8 @@ qt_internal_add_module(Gui
math3d/qvector4d.h
math3d/qvectornd.cpp math3d/qvectornd.h
painting/qbackingstore.cpp painting/qbackingstore.h
+ painting/qbackingstoredefaultcompositor.cpp painting/qbackingstoredefaultcompositor_p.h
+ painting/qbackingstorerhisupport.cpp painting/qbackingstorerhisupport_p.h
painting/qbezier.cpp painting/qbezier_p.h
painting/qblendfunctions.cpp painting/qblendfunctions_p.h
painting/qblittable.cpp painting/qblittable_p.h
@@ -289,6 +291,14 @@ qt_internal_add_resource(Gui "qpdf"
${qpdf_resource_files}
)
+qt_internal_add_resource(Gui "gui_shaders"
+ PREFIX
+ "/qt-project.org/gui"
+ FILES
+ "painting/shaders/backingstorecompose.vert.qsb"
+ "painting/shaders/backingstorecompose.frag.qsb"
+)
+
if(QT_FEATURE_reduce_relocations AND UNIX AND GCC)
target_link_options(Gui PRIVATE
"LINKER:--dynamic-list=${CMAKE_CURRENT_LIST_DIR}/QtGui.dynlist")
@@ -358,6 +368,7 @@ qt_internal_extend_target(Gui CONDITION APPLE
image/qimage_darwin.mm
painting/qcoregraphics.mm painting/qcoregraphics_p.h
painting/qrasterbackingstore.cpp painting/qrasterbackingstore_p.h
+ painting/qrhibackingstore.cpp painting/qrhibackingstore_p.h
platform/darwin/qmacmime.mm platform/darwin/qmacmime_p.h
platform/darwin/qapplekeymapper.mm platform/darwin/qapplekeymapper_p.h
text/coretext/qcoretextfontdatabase.mm text/coretext/qcoretextfontdatabase_p.h
diff --git a/src/gui/kernel/qwindow.cpp b/src/gui/kernel/qwindow.cpp
index ec0ee73660..c1373b3224 100644
--- a/src/gui/kernel/qwindow.cpp
+++ b/src/gui/kernel/qwindow.cpp
@@ -2867,13 +2867,6 @@ QWindow *QWindowPrivate::topLevelWindow(QWindow::AncestorMode mode) const
return window;
}
-#if QT_CONFIG(opengl)
-QOpenGLContext *QWindowPrivate::shareContext() const
-{
- return qt_gl_global_share_context();
-};
-#endif
-
/*!
Creates a local representation of a window created by another process or by
using native libraries below Qt.
diff --git a/src/gui/kernel/qwindow_p.h b/src/gui/kernel/qwindow_p.h
index dafd09b1e2..83dbbf8c5d 100644
--- a/src/gui/kernel/qwindow_p.h
+++ b/src/gui/kernel/qwindow_p.h
@@ -87,10 +87,6 @@ public:
QWindow *topLevelWindow(QWindow::AncestorMode mode = QWindow::IncludeTransients) const;
-#if QT_CONFIG(opengl)
- virtual QOpenGLContext *shareContext() const;
-#endif
-
virtual QWindow *eventReceiver() { Q_Q(QWindow); return q; }
virtual void setVisible(bool visible);
diff --git a/src/gui/painting/qbackingstore.cpp b/src/gui/painting/qbackingstore.cpp
index 762938e764..bd3e54a5c0 100644
--- a/src/gui/painting/qbackingstore.cpp
+++ b/src/gui/painting/qbackingstore.cpp
@@ -218,13 +218,6 @@ void QBackingStore::flush(const QRegion &region, QWindow *window, const QPoint &
return;
}
- if (!QPlatformSurface::isRasterSurface(window)) {
- qWarning() << "Attempted flush to non-raster surface" << window << "of type" << window->surfaceType()
- << (window->inherits("QWidgetWindow") ? "(consider using Qt::WA_PaintOnScreen to exclude "
- "from backingstore sync)" : "");
- return;
- }
-
Q_ASSERT(window == topLevelWindow || topLevelWindow->isAncestorOf(window, QWindow::ExcludeTransients));
handle()->flush(window, QHighDpi::toNativeLocalRegion(region, window),
diff --git a/src/gui/painting/qbackingstoredefaultcompositor.cpp b/src/gui/painting/qbackingstoredefaultcompositor.cpp
new file mode 100644
index 0000000000..ea55b4c230
--- /dev/null
+++ b/src/gui/painting/qbackingstoredefaultcompositor.cpp
@@ -0,0 +1,630 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the plugins of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qbackingstoredefaultcompositor_p.h"
+#include <QtGui/private/qwindow_p.h>
+#include <qpa/qplatformgraphicsbuffer.h>
+#include <QtCore/qfile.h>
+
+QT_BEGIN_NAMESPACE
+
+QBackingStoreDefaultCompositor::~QBackingStoreDefaultCompositor()
+{
+ reset();
+}
+
+void QBackingStoreDefaultCompositor::reset()
+{
+ delete m_psNoBlend;
+ m_psNoBlend = nullptr;
+ delete m_psBlend;
+ m_psBlend = nullptr;
+ delete m_psPremulBlend;
+ m_psPremulBlend = nullptr;
+ delete m_sampler;
+ m_sampler = nullptr;
+ delete m_vbuf;
+ m_vbuf = nullptr;
+ delete m_texture;
+ m_texture = nullptr;
+ m_widgetQuadData.reset();
+ for (PerQuadData &d : m_textureQuadData)
+ d.reset();
+}
+
+QRhiTexture *QBackingStoreDefaultCompositor::toTexture(const QPlatformBackingStore *backingStore,
+ QRhi *rhi,
+ QRhiResourceUpdateBatch *resourceUpdates,
+ const QRegion &dirtyRegion,
+ QPlatformBackingStore::TextureFlags *flags) const
+{
+ return toTexture(backingStore->toImage(), rhi, resourceUpdates, dirtyRegion, flags);
+}
+
+QRhiTexture *QBackingStoreDefaultCompositor::toTexture(const QImage &sourceImage,
+ QRhi *rhi,
+ QRhiResourceUpdateBatch *resourceUpdates,
+ const QRegion &dirtyRegion,
+ QPlatformBackingStore::TextureFlags *flags) const
+{
+ Q_ASSERT(rhi);
+ Q_ASSERT(resourceUpdates);
+ Q_ASSERT(flags);
+
+ if (!m_rhi) {
+ m_rhi = rhi;
+ } else if (m_rhi != rhi) {
+ qWarning("QBackingStoreDefaultCompositor: the QRhi has changed unexpectedly, this should not happen");
+ return nullptr;
+ }
+
+ QImage image = sourceImage;
+
+ bool needsConversion = false;
+ *flags = {};
+
+ switch (image.format()) {
+ case QImage::Format_ARGB32_Premultiplied:
+ *flags |= QPlatformBackingStore::TexturePremultiplied;
+ Q_FALLTHROUGH();
+ case QImage::Format_RGB32:
+ case QImage::Format_ARGB32:
+#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
+ *flags |= QPlatformBackingStore::TextureSwizzle;
+#endif
+ break;
+ case QImage::Format_RGBA8888_Premultiplied:
+ *flags |= QPlatformBackingStore::TexturePremultiplied;
+ Q_FALLTHROUGH();
+ case QImage::Format_RGBX8888:
+ case QImage::Format_RGBA8888:
+ break;
+ case QImage::Format_BGR30:
+ case QImage::Format_A2BGR30_Premultiplied:
+ // no fast path atm
+ needsConversion = true;
+ break;
+ case QImage::Format_RGB30:
+ case QImage::Format_A2RGB30_Premultiplied:
+ // no fast path atm
+ needsConversion = true;
+ break;
+ default:
+ needsConversion = true;
+ break;
+ }
+
+ if (image.size().isEmpty())
+ return nullptr;
+
+ const bool resized = !m_texture || m_texture->pixelSize() != image.size();
+ if (dirtyRegion.isEmpty() && !resized)
+ return m_texture;
+
+ if (needsConversion)
+ image = image.convertToFormat(QImage::Format_RGBA8888);
+ else
+ image.detach(); // if it was just wrapping data, that's no good, we need ownership, so detach
+
+ if (resized) {
+ if (!m_texture)
+ m_texture = rhi->newTexture(QRhiTexture::RGBA8, image.size());
+ else
+ m_texture->setPixelSize(image.size());
+ m_texture->create();
+ resourceUpdates->uploadTexture(m_texture, image);
+ } else {
+ QRect imageRect = image.rect();
+ QRect rect = dirtyRegion.boundingRect() & imageRect;
+ QRhiTextureSubresourceUploadDescription subresDesc(image);
+ subresDesc.setSourceTopLeft(rect.topLeft());
+ subresDesc.setSourceSize(rect.size());
+ subresDesc.setDestinationTopLeft(rect.topLeft());
+ QRhiTextureUploadDescription uploadDesc(QRhiTextureUploadEntry(0, 0, subresDesc));
+ resourceUpdates->uploadTexture(m_texture, uploadDesc);
+ }
+
+ return m_texture;
+}
+
+static inline QRect deviceRect(const QRect &rect, QWindow *window)
+{
+ return QRect(rect.topLeft() * window->devicePixelRatio(),
+ rect.size() * window->devicePixelRatio());
+}
+
+static inline QPoint deviceOffset(const QPoint &pt, QWindow *window)
+{
+ return pt * window->devicePixelRatio();
+}
+
+static QRegion deviceRegion(const QRegion &region, QWindow *window, const QPoint &offset)
+{
+ if (offset.isNull() && window->devicePixelRatio() <= 1)
+ return region;
+
+ QVarLengthArray<QRect, 4> rects;
+ rects.reserve(region.rectCount());
+ for (const QRect &rect : region)
+ rects.append(deviceRect(rect.translated(offset), window));
+
+ QRegion deviceRegion;
+ deviceRegion.setRects(rects.constData(), rects.count());
+ return deviceRegion;
+}
+
+static QMatrix4x4 targetTransform(const QRectF &target, const QRect &viewport, bool invertY)
+{
+ qreal x_scale = target.width() / viewport.width();
+ qreal y_scale = target.height() / viewport.height();
+
+ const QPointF relative_to_viewport = target.topLeft() - viewport.topLeft();
+ qreal x_translate = x_scale - 1 + ((relative_to_viewport.x() / viewport.width()) * 2);
+ qreal y_translate;
+ if (invertY)
+ y_translate = y_scale - 1 + ((relative_to_viewport.y() / viewport.height()) * 2);
+ else
+ y_translate = -y_scale + 1 - ((relative_to_viewport.y() / viewport.height()) * 2);
+
+ QMatrix4x4 matrix;
+ matrix(0,3) = x_translate;
+ matrix(1,3) = y_translate;
+
+ matrix(0,0) = x_scale;
+ matrix(1,1) = y_scale;
+
+ return matrix;
+}
+
+enum class SourceTransformOrigin {
+ BottomLeft,
+ TopLeft
+};
+
+static QMatrix3x3 sourceTransform(const QRectF &subTexture,
+ const QSize &textureSize,
+ SourceTransformOrigin origin)
+{
+ qreal x_scale = subTexture.width() / textureSize.width();
+ qreal y_scale = subTexture.height() / textureSize.height();
+
+ const QPointF topLeft = subTexture.topLeft();
+ qreal x_translate = topLeft.x() / textureSize.width();
+ qreal y_translate = topLeft.y() / textureSize.height();
+
+ if (origin == SourceTransformOrigin::TopLeft) {
+ y_scale = -y_scale;
+ y_translate = 1 - y_translate;
+ }
+
+ QMatrix3x3 matrix;
+ matrix(0,2) = x_translate;
+ matrix(1,2) = y_translate;
+
+ matrix(0,0) = x_scale;
+ matrix(1,1) = y_scale;
+
+ return matrix;
+}
+
+static inline QRect toBottomLeftRect(const QRect &topLeftRect, int windowHeight)
+{
+ return QRect(topLeftRect.x(), windowHeight - topLeftRect.bottomRight().y() - 1,
+ topLeftRect.width(), topLeftRect.height());
+}
+
+static bool prepareDrawForRenderToTextureWidget(const QPlatformTextureList *textures,
+ int idx,
+ QWindow *window,
+ const QRect &deviceWindowRect,
+ const QPoint &offset,
+ bool invertTargetY,
+ bool invertSource,
+ QMatrix4x4 *target,
+ QMatrix3x3 *source)
+{
+ const QRect clipRect = textures->clipRect(idx);
+ if (clipRect.isEmpty())
+ return false;
+
+ QRect rectInWindow = textures->geometry(idx);
+ // relative to the TLW, not necessarily our window (if the flush is for a native child widget), have to adjust
+ rectInWindow.translate(-offset);
+
+ const QRect clippedRectInWindow = rectInWindow & clipRect.translated(rectInWindow.topLeft());
+ const QRect srcRect = toBottomLeftRect(clipRect, rectInWindow.height());
+
+ *target = targetTransform(deviceRect(clippedRectInWindow, window),
+ deviceWindowRect,
+ invertTargetY);
+
+ *source = sourceTransform(deviceRect(srcRect, window),
+ deviceRect(rectInWindow, window).size(),
+ invertSource ? SourceTransformOrigin::TopLeft : SourceTransformOrigin::BottomLeft);
+
+ return true;
+}
+
+static QShader getShader(const QString &name)
+{
+ QFile f(name);
+ if (f.open(QIODevice::ReadOnly))
+ return QShader::fromSerialized(f.readAll());
+
+ qWarning("QBackingStoreDefaultCompositor: Could not find built-in shader %s "
+ "(is something wrong with QtGui library resources?)",
+ qPrintable(name));
+ return QShader();
+}
+
+static void updateMatrix3x3(QRhiResourceUpdateBatch *resourceUpdates, QRhiBuffer *ubuf, const QMatrix3x3 &m)
+{
+ // mat3 is still 4 floats per column in the uniform buffer (but there is no
+ // 4th column), so 48 bytes altogether, not 36 or 64.
+
+ float f[12];
+ const float *src = static_cast<const float *>(m.constData());
+ float *dst = f;
+ memcpy(dst, src, 3 * sizeof(float));
+ memcpy(dst + 4, src + 3, 3 * sizeof(float));
+ memcpy(dst + 8, src + 6, 3 * sizeof(float));
+
+ resourceUpdates->updateDynamicBuffer(ubuf, 64, 48, f);
+}
+
+enum class PipelineBlend {
+ None,
+ Alpha,
+ PremulAlpha
+};
+
+static QRhiGraphicsPipeline *createGraphicsPipeline(QRhi *rhi,
+ QRhiShaderResourceBindings *srb,
+ QRhiSwapChain *swapchain,
+ PipelineBlend blend)
+{
+ QRhiGraphicsPipeline *ps = rhi->newGraphicsPipeline();
+
+ switch (blend) {
+ case PipelineBlend::Alpha:
+ {
+ QRhiGraphicsPipeline::TargetBlend blend;
+ blend.enable = true;
+ blend.srcColor = QRhiGraphicsPipeline::SrcAlpha;
+ blend.dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
+ blend.srcAlpha = QRhiGraphicsPipeline::One;
+ blend.dstAlpha = QRhiGraphicsPipeline::One;
+ ps->setTargetBlends({ blend });
+ }
+ break;
+ case PipelineBlend::PremulAlpha:
+ {
+ QRhiGraphicsPipeline::TargetBlend blend;
+ blend.enable = true;
+ blend.srcColor = QRhiGraphicsPipeline::One;
+ blend.dstColor = QRhiGraphicsPipeline::OneMinusSrcAlpha;
+ blend.srcAlpha = QRhiGraphicsPipeline::One;
+ blend.dstAlpha = QRhiGraphicsPipeline::One;
+ ps->setTargetBlends({ blend });
+ }
+ break;
+ default:
+ break;
+ }
+
+ ps->setShaderStages({
+ { QRhiShaderStage::Vertex, getShader(QLatin1String(":/qt-project.org/gui/painting/shaders/backingstorecompose.vert.qsb")) },
+ { QRhiShaderStage::Fragment, getShader(QLatin1String(":/qt-project.org/gui/painting/shaders/backingstorecompose.frag.qsb")) }
+ });
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({ { 5 * sizeof(float) } });
+ inputLayout.setAttributes({
+ { 0, 0, QRhiVertexInputAttribute::Float3, 0 },
+ { 0, 1, QRhiVertexInputAttribute::Float2, quint32(3 * sizeof(float)) }
+ });
+ ps->setVertexInputLayout(inputLayout);
+ ps->setShaderResourceBindings(srb);
+ ps->setRenderPassDescriptor(swapchain->renderPassDescriptor());
+
+ if (!ps->create()) {
+ qWarning("QBackingStoreDefaultCompositor: Failed to build graphics pipeline");
+ delete ps;
+ return nullptr;
+ }
+ return ps;
+}
+
+static const int UBUF_SIZE = 120;
+
+QBackingStoreDefaultCompositor::PerQuadData QBackingStoreDefaultCompositor::createPerQuadData(QRhiTexture *texture)
+{
+ PerQuadData d;
+
+ d.ubuf = m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, UBUF_SIZE);
+ if (!d.ubuf->create())
+ qWarning("QBackingStoreDefaultCompositor: Failed to create uniform buffer");
+
+ d.srb = m_rhi->newShaderResourceBindings();
+ d.srb->setBindings({
+ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.ubuf, 0, UBUF_SIZE),
+ QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, texture, m_sampler)
+ });
+ if (!d.srb->create())
+ qWarning("QBackingStoreDefaultCompositor: Failed to create srb");
+
+ d.lastUsedTexture = texture;
+
+ return d;
+}
+
+void QBackingStoreDefaultCompositor::updatePerQuadData(PerQuadData *d, QRhiTexture *texture)
+{
+ // This whole check-if-texture-ptr-is-different is needed because there is
+ // nothing saying a QPlatformTextureList cannot return a different
+ // QRhiTexture* from the same index in a subsequent flush.
+
+ if (d->lastUsedTexture == texture || !d->srb)
+ return;
+
+ d->srb->setBindings({
+ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d->ubuf, 0, UBUF_SIZE),
+ QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, texture, m_sampler)
+ });
+
+ d->srb->updateResources(QRhiShaderResourceBindings::BindingsAreSorted);
+
+ d->lastUsedTexture = texture;
+}
+
+void QBackingStoreDefaultCompositor::updateUniforms(PerQuadData *d, QRhiResourceUpdateBatch *resourceUpdates,
+ const QMatrix4x4 &target, const QMatrix3x3 &source, bool needsRedBlueSwap)
+{
+ resourceUpdates->updateDynamicBuffer(d->ubuf, 0, 64, target.constData());
+ updateMatrix3x3(resourceUpdates, d->ubuf, source);
+ float opacity = 1.0f;
+ resourceUpdates->updateDynamicBuffer(d->ubuf, 112, 4, &opacity);
+ qint32 swapRedBlue = needsRedBlueSwap ? 1 : 0;
+ resourceUpdates->updateDynamicBuffer(d->ubuf, 116, 4, &swapRedBlue);
+}
+
+void QBackingStoreDefaultCompositor::ensureResources(QRhiSwapChain *swapchain, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ static const float vertexData[] = {
+ -1, -1, 0, 0, 0,
+ -1, 1, 0, 0, 1,
+ 1, -1, 0, 1, 0,
+ -1, 1, 0, 0, 1,
+ 1, -1, 0, 1, 0,
+ 1, 1, 0, 1, 1
+ };
+
+ if (!m_vbuf) {
+ m_vbuf = m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData));
+ if (m_vbuf->create())
+ resourceUpdates->uploadStaticBuffer(m_vbuf, vertexData);
+ else
+ qWarning("QBackingStoreDefaultCompositor: Failed to create vertex buffer");
+ }
+
+ if (!m_sampler) {
+ m_sampler = m_rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
+ QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
+ if (!m_sampler->create())
+ qWarning("QBackingStoreDefaultCompositor: Failed to create sampler");
+ }
+
+ if (!m_widgetQuadData.isValid())
+ m_widgetQuadData = createPerQuadData(m_texture);
+
+ QRhiShaderResourceBindings *srb = m_widgetQuadData.srb; // just for the layout
+ if (!m_psNoBlend)
+ m_psNoBlend = createGraphicsPipeline(m_rhi, srb, swapchain, PipelineBlend::None);
+ if (!m_psBlend)
+ m_psBlend = createGraphicsPipeline(m_rhi, srb, swapchain, PipelineBlend::Alpha);
+ if (!m_psPremulBlend)
+ m_psPremulBlend = createGraphicsPipeline(m_rhi, srb, swapchain, PipelineBlend::PremulAlpha);
+}
+
+QPlatformBackingStore::FlushResult QBackingStoreDefaultCompositor::flush(QPlatformBackingStore *backingStore,
+ QRhi *rhi,
+ QRhiSwapChain *swapchain,
+ QWindow *window,
+ const QRegion &region,
+ const QPoint &offset,
+ QPlatformTextureList *textures,
+ bool translucentBackground)
+{
+ Q_ASSERT(textures); // may be empty if there are no render-to-texture widgets at all, but null it cannot be
+
+ if (!m_rhi) {
+ m_rhi = rhi;
+ } else if (m_rhi != rhi) {
+ qWarning("QBackingStoreDefaultCompositor: the QRhi has changed unexpectedly, this should not happen");
+ return QPlatformBackingStore::FlushFailed;
+ }
+
+ if (!qt_window_private(window)->receivedExpose)
+ return QPlatformBackingStore::FlushSuccess;
+
+ qCDebug(lcQpaBackingStore) << "Composing and flushing" << region << "of" << window
+ << "at offset" << offset << "with" << textures->count() << "texture(s) in" << textures
+ << "via swapchain" << swapchain;
+
+ QWindowPrivate::get(window)->lastComposeTime.start();
+
+ if (swapchain->currentPixelSize() != swapchain->surfacePixelSize())
+ swapchain->createOrResize();
+
+ // Start recording a new frame.
+ QRhi::FrameOpResult frameResult = rhi->beginFrame(swapchain);
+ if (frameResult == QRhi::FrameOpSwapChainOutOfDate) {
+ if (!swapchain->createOrResize())
+ return QPlatformBackingStore::FlushFailed;
+ frameResult = rhi->beginFrame(swapchain);
+ }
+ if (frameResult == QRhi::FrameOpDeviceLost)
+ return QPlatformBackingStore::FlushFailedDueToLostDevice;
+ if (frameResult != QRhi::FrameOpSuccess)
+ return QPlatformBackingStore::FlushFailed;
+
+ // Prepare resource updates.
+ QRhiResourceUpdateBatch *resourceUpdates = rhi->nextResourceUpdateBatch();
+ QPlatformBackingStore::TextureFlags flags;
+
+ bool gotTextureFromGraphicsBuffer = false;
+ if (QPlatformGraphicsBuffer *graphicsBuffer = backingStore->graphicsBuffer()) {
+ if (graphicsBuffer->lock(QPlatformGraphicsBuffer::SWReadAccess)) {
+ const QImage::Format format = QImage::toImageFormat(graphicsBuffer->format());
+ const QSize size = graphicsBuffer->size();
+ QImage wrapperImage(graphicsBuffer->data(), size.width(), size.height(), graphicsBuffer->bytesPerLine(), format);
+ toTexture(wrapperImage, rhi, resourceUpdates, deviceRegion(region, window, offset), &flags);
+ gotTextureFromGraphicsBuffer = true;
+ graphicsBuffer->unlock();
+ if (graphicsBuffer->origin() == QPlatformGraphicsBuffer::OriginBottomLeft)
+ flags |= QPlatformBackingStore::TextureFlip;
+ }
+ }
+ if (!gotTextureFromGraphicsBuffer)
+ toTexture(backingStore, rhi, resourceUpdates, deviceRegion(region, window, offset), &flags);
+
+ ensureResources(swapchain, resourceUpdates);
+
+ const bool needsRedBlueSwap = (flags & QPlatformBackingStore::TextureSwizzle) != 0;
+ const bool premultiplied = (flags & QPlatformBackingStore::TexturePremultiplied) != 0;
+ SourceTransformOrigin origin = SourceTransformOrigin::TopLeft;
+ if (flags & QPlatformBackingStore::TextureFlip)
+ origin = SourceTransformOrigin::BottomLeft;
+
+ const QRect deviceWindowRect = deviceRect(QRect(QPoint(), window->size()), window);
+ const QPoint deviceWindowOffset = deviceOffset(offset, window);
+
+ const bool invertTargetY = rhi->clipSpaceCorrMatrix().data()[5] < 0.0f;
+ const bool invertSource = rhi->isYUpInFramebuffer() != rhi->isYUpInNDC();
+ if (m_texture) {
+ // The backingstore is for the entire tlw.
+ // In case of native children offset tells the position relative to the tlw.
+ const QRect srcRect = toBottomLeftRect(deviceWindowRect.translated(deviceWindowOffset), m_texture->pixelSize().height());
+ const QMatrix3x3 source = sourceTransform(srcRect, m_texture->pixelSize(), origin);
+ QMatrix4x4 target; // identity
+ if (invertTargetY)
+ target.data()[5] = -1.0f;
+ updateUniforms(&m_widgetQuadData, resourceUpdates, target, source, needsRedBlueSwap);
+ }
+
+ const int textureWidgetCount = textures->count();
+ const int oldTextureQuadDataCount = m_textureQuadData.count();
+ if (oldTextureQuadDataCount != textureWidgetCount) {
+ for (int i = textureWidgetCount; i < oldTextureQuadDataCount; ++i)
+ m_textureQuadData[i].reset();
+ m_textureQuadData.resize(textureWidgetCount);
+ }
+
+ for (int i = 0; i < textureWidgetCount; ++i) {
+ QMatrix4x4 target;
+ QMatrix3x3 source;
+ if (!prepareDrawForRenderToTextureWidget(textures, i, window, deviceWindowRect,
+ offset, invertTargetY, invertSource, &target, &source))
+ {
+ m_textureQuadData[i].reset();
+ continue;
+ }
+ QRhiTexture *t = textures->texture(i);
+ if (t) {
+ if (!m_textureQuadData[i].isValid())
+ m_textureQuadData[i] = createPerQuadData(t);
+ else
+ updatePerQuadData(&m_textureQuadData[i], t);
+ updateUniforms(&m_textureQuadData[i], resourceUpdates, target, source, false);
+ } else {
+ m_textureQuadData[i].reset();
+ }
+ }
+
+ // Record the render pass (with committing the resource updates).
+ QRhiCommandBuffer *cb = swapchain->currentFrameCommandBuffer();
+ const QSize outputSizeInPixels = swapchain->currentPixelSize();
+ QColor clearColor = translucentBackground ? Qt::transparent : Qt::black;
+ cb->beginPass(swapchain->currentFrameRenderTarget(), clearColor, { 1.0f, 0 }, resourceUpdates);
+
+ cb->setGraphicsPipeline(m_psNoBlend);
+ cb->setViewport({ 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) });
+ QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf, 0);
+ cb->setVertexInput(0, 1, &vbufBinding);
+
+ // Textures for renderToTexture widgets.
+ for (int i = 0; i < textureWidgetCount; ++i) {
+ if (!textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop)) {
+ if (m_textureQuadData[i].isValid()) {
+ cb->setShaderResources(m_textureQuadData[i].srb);
+ cb->draw(6);
+ }
+ }
+ }
+
+ cb->setGraphicsPipeline(premultiplied ? m_psPremulBlend : m_psBlend);
+
+ // Backingstore texture with the normal widgets.
+ if (m_texture) {
+ cb->setShaderResources(m_widgetQuadData.srb);
+ cb->draw(6);
+ }
+
+ // Textures for renderToTexture widgets that have WA_AlwaysStackOnTop set.
+ for (int i = 0; i < textureWidgetCount; ++i) {
+ const QPlatformTextureList::Flags flags = textures->flags(i);
+ if (flags.testFlag(QPlatformTextureList::StacksOnTop)) {
+ if (m_textureQuadData[i].isValid()) {
+ if (flags.testFlag(QPlatformTextureList::NeedsPremultipliedAlphaBlending))
+ cb->setGraphicsPipeline(m_psPremulBlend);
+ else
+ cb->setGraphicsPipeline(m_psBlend);
+ cb->setShaderResources(m_textureQuadData[i].srb);
+ cb->draw(6);
+ }
+ }
+ }
+
+ cb->endPass();
+
+ rhi->endFrame(swapchain);
+
+ return QPlatformBackingStore::FlushSuccess;
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/painting/qbackingstoredefaultcompositor_p.h b/src/gui/painting/qbackingstoredefaultcompositor_p.h
new file mode 100644
index 0000000000..e316ba14e5
--- /dev/null
+++ b/src/gui/painting/qbackingstoredefaultcompositor_p.h
@@ -0,0 +1,123 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QBACKINGSTOREDEFAULTCOMPOSITOR_P_H
+#define QBACKINGSTOREDEFAULTCOMPOSITOR_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qpa/qplatformbackingstore.h>
+#include <QtGui/private/qrhi_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QBackingStoreDefaultCompositor
+{
+public:
+ ~QBackingStoreDefaultCompositor();
+
+ void reset();
+
+ QRhiTexture *toTexture(const QPlatformBackingStore *backingStore,
+ QRhi *rhi,
+ QRhiResourceUpdateBatch *resourceUpdates,
+ const QRegion &dirtyRegion,
+ QPlatformBackingStore::TextureFlags *flags) const;
+
+ QPlatformBackingStore::FlushResult flush(QPlatformBackingStore *backingStore,
+ QRhi *rhi,
+ QRhiSwapChain *swapchain,
+ QWindow *window,
+ const QRegion &region,
+ const QPoint &offset,
+ QPlatformTextureList *textures,
+ bool translucentBackground);
+
+private:
+ void ensureResources(QRhiSwapChain *swapchain, QRhiResourceUpdateBatch *resourceUpdates);
+ QRhiTexture *toTexture(const QImage &image,
+ QRhi *rhi,
+ QRhiResourceUpdateBatch *resourceUpdates,
+ const QRegion &dirtyRegion,
+ QPlatformBackingStore::TextureFlags *flags) const;
+
+ mutable QRhi *m_rhi = nullptr;
+ mutable QRhiTexture *m_texture = nullptr;
+
+ QRhiBuffer *m_vbuf = nullptr;
+ QRhiSampler *m_sampler = nullptr;
+ QRhiGraphicsPipeline *m_psNoBlend = nullptr;
+ QRhiGraphicsPipeline *m_psBlend = nullptr;
+ QRhiGraphicsPipeline *m_psPremulBlend = nullptr;
+
+ struct PerQuadData {
+ QRhiBuffer *ubuf = nullptr;
+ // All srbs are layout-compatible.
+ QRhiShaderResourceBindings *srb = nullptr;
+ QRhiTexture *lastUsedTexture = nullptr;
+ bool isValid() const { return ubuf && srb; }
+ void reset() {
+ delete ubuf;
+ ubuf = nullptr;
+ delete srb;
+ srb = nullptr;
+ lastUsedTexture = nullptr;
+ }
+ };
+ PerQuadData m_widgetQuadData;
+ QVarLengthArray<PerQuadData, 8> m_textureQuadData;
+
+ PerQuadData createPerQuadData(QRhiTexture *texture);
+ void updatePerQuadData(PerQuadData *d, QRhiTexture *texture);
+ void updateUniforms(PerQuadData *d, QRhiResourceUpdateBatch *resourceUpdates,
+ const QMatrix4x4 &target, const QMatrix3x3 &source, bool needsRedBlueSwap);
+};
+
+QT_END_NAMESPACE
+
+#endif // QBACKINGSTOREDEFAULTCOMPOSITOR_P_H
diff --git a/src/gui/painting/qbackingstorerhisupport.cpp b/src/gui/painting/qbackingstorerhisupport.cpp
new file mode 100644
index 0000000000..4e87eefc34
--- /dev/null
+++ b/src/gui/painting/qbackingstorerhisupport.cpp
@@ -0,0 +1,355 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qbackingstorerhisupport_p.h"
+#include <qpa/qplatformintegration.h>
+#include <private/qguiapplication_p.h>
+
+#if QT_CONFIG(opengl)
+#include <QtGui/qoffscreensurface.h>
+#include <QtGui/private/qopenglcontext_p.h>
+#include <QtGui/private/qrhigles2_p.h>
+#endif
+
+#ifdef Q_OS_WIN
+#include <QtGui/private/qrhid3d11_p.h>
+#endif
+
+#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#include <QtGui/private/qrhimetal_p.h>
+#endif
+
+#if QT_CONFIG(vulkan)
+#include <QtGui/private/qrhivulkan_p.h>
+#include <QtGui/private/qvulkandefaultinstance_p.h>
+#endif
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(lcQpaBackingStore)
+
+QBackingStoreRhiSupport::~QBackingStoreRhiSupport()
+{
+ reset();
+}
+
+void QBackingStoreRhiSupport::SwapchainData::reset()
+{
+ delete swapchain;
+ delete renderPassDescriptor;
+ delete windowWatcher;
+ *this = {};
+}
+
+void QBackingStoreRhiSupport::reset()
+{
+ for (SwapchainData &d : m_swapchains)
+ d.reset();
+
+ m_swapchains.clear();
+
+ delete m_rhi;
+ m_rhi = nullptr;
+
+ delete m_openGLFallbackSurface;
+ m_openGLFallbackSurface = nullptr;
+}
+
+bool QBackingStoreRhiSupport::create()
+{
+ if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::RhiBasedRendering))
+ return false;
+
+ // note: m_window may be null (special case for fully offscreen rendering)
+
+ QRhi *rhi = nullptr;
+ QOffscreenSurface *surface = nullptr;
+ QRhi::Flags flags;
+
+#if QT_CONFIG(opengl)
+ if (!rhi && m_config.api() == QPlatformBackingStoreRhiConfig::OpenGL) {
+ surface = QRhiGles2InitParams::newFallbackSurface(m_format);
+ QRhiGles2InitParams params;
+ params.fallbackSurface = surface;
+ params.window = m_window;
+ params.format = m_format;
+ params.shareContext = qt_gl_global_share_context();
+ rhi = QRhi::create(QRhi::OpenGLES2, &params, flags);
+ }
+#endif
+
+#ifdef Q_OS_WIN
+ if (!rhi && m_config.api() == QPlatformBackingStoreRhiConfig::D3D11) {
+ QRhiD3D11InitParams params;
+ params.enableDebugLayer = m_config.isDebugLayerEnabled();
+ rhi = QRhi::create(QRhi::D3D11, &params, flags);
+ }
+#endif
+
+#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+ if (!rhi && m_config.api() == QPlatformBackingStoreRhiConfig::Metal) {
+ QRhiMetalInitParams params;
+ // For parity with Qt Quick, fall back to OpenGL when there is no Metal (f.ex. in macOS virtual machines).
+ if (QRhi::probe(QRhi::Metal, &params)) {
+ rhi = QRhi::create(QRhi::Metal, &params, flags);
+ } else {
+ qCDebug(lcQpaBackingStore, "Metal does not seem to be supported. Falling back to OpenGL.");
+ rhi = QRhi::create(QRhi::OpenGLES2, &params, flags);
+ }
+ }
+#endif
+
+#if QT_CONFIG(vulkan)
+ if (!rhi && m_config.api() == QPlatformBackingStoreRhiConfig::Vulkan) {
+ if (m_config.isDebugLayerEnabled())
+ QVulkanDefaultInstance::setFlag(QVulkanDefaultInstance::EnableValidation);
+ QRhiVulkanInitParams params;
+ if (m_window) {
+ if (!m_window->vulkanInstance())
+ m_window->setVulkanInstance(QVulkanDefaultInstance::instance());
+ params.inst = m_window->vulkanInstance();
+ } else {
+ params.inst = QVulkanDefaultInstance::instance();
+ }
+ if (!params.inst) {
+ qWarning("No QVulkanInstance set for the top-level window, this is wrong.");
+ return false;
+ }
+ params.window = m_window;
+ params.deviceExtensions = m_config.deviceExtensions();
+ rhi = QRhi::create(QRhi::Vulkan, &params, flags);
+ }
+#endif
+
+ if (!rhi) {
+ qWarning("Failed to create QRhi for QBackingStoreRhiSupport");
+ delete surface;
+ return false;
+ }
+
+ m_rhi = rhi;
+ m_openGLFallbackSurface = surface;
+ return true;
+}
+
+QRhiSwapChain *QBackingStoreRhiSupport::swapChainForWindow(QWindow *window)
+{
+ auto it = m_swapchains.constFind(window);
+ if (it != m_swapchains.constEnd())
+ return it.value().swapchain;
+
+ QRhiSwapChain *swapchain = nullptr;
+ QRhiRenderPassDescriptor *rp = nullptr;
+ if (window && m_rhi) {
+ QRhiSwapChain::Flags flags;
+ const QSurfaceFormat format = window->requestedFormat();
+ if (format.swapInterval() == 0)
+ flags |= QRhiSwapChain::NoVSync;
+ if (format.alphaBufferSize() > 0)
+ flags |= QRhiSwapChain::SurfaceHasNonPreMulAlpha;
+#if QT_CONFIG(vulkan)
+ if (m_config.api() == QPlatformBackingStoreRhiConfig::Vulkan && !window->vulkanInstance())
+ window->setVulkanInstance(QVulkanDefaultInstance::instance());
+#endif
+ qCDebug(lcQpaBackingStore) << "Creating swapchain for window" << window;
+ swapchain = m_rhi->newSwapChain();
+ swapchain->setWindow(window);
+ swapchain->setFlags(flags);
+ rp = swapchain->newCompatibleRenderPassDescriptor();
+ swapchain->setRenderPassDescriptor(rp);
+ if (!swapchain->createOrResize()) {
+ qWarning("Failed to create swapchain for window flushed with an RHI-enabled backingstore");
+ delete rp;
+ return nullptr;
+ }
+ }
+ if (swapchain) {
+ SwapchainData d;
+ d.swapchain = swapchain;
+ d.renderPassDescriptor = rp;
+ d.windowWatcher = new QBackingStoreRhiSupportWindowWatcher(this);
+ m_swapchains.insert(window, d);
+ window->installEventFilter(d.windowWatcher);
+ }
+ return swapchain;
+}
+
+bool QBackingStoreRhiSupportWindowWatcher::eventFilter(QObject *obj, QEvent *event)
+{
+ if (event->type() == QEvent::PlatformSurface
+ && static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed)
+ {
+ QWindow *window = qobject_cast<QWindow *>(obj);
+ auto it = m_rhiSupport->m_swapchains.find(window);
+ if (it != m_rhiSupport->m_swapchains.end()) {
+ qCDebug(lcQpaBackingStore) << "SurfaceAboutToBeDestroyed received for tracked window" << window << "cleaning up swapchain";
+ it->reset();
+ m_rhiSupport->m_swapchains.erase(it);
+ }
+ }
+ return QObject::eventFilter(obj, event);
+}
+
+QSurface::SurfaceType QBackingStoreRhiSupport::surfaceTypeForConfig(const QPlatformBackingStoreRhiConfig &config)
+{
+ QSurface::SurfaceType type = QSurface::RasterSurface;
+ switch (config.api()) {
+ case QPlatformBackingStoreRhiConfig::D3D11:
+ type = QSurface::Direct3DSurface;
+ break;
+ case QPlatformBackingStoreRhiConfig::Vulkan:
+ type = QSurface::VulkanSurface;
+ break;
+ case QPlatformBackingStoreRhiConfig::Metal:
+ type = QSurface::MetalSurface;
+ break;
+ case QPlatformBackingStoreRhiConfig::OpenGL:
+ type = QSurface::OpenGLSurface;
+ break;
+ default:
+ break;
+ }
+ return type;
+}
+
+QRhi::Implementation QBackingStoreRhiSupport::apiToRhiBackend(QPlatformBackingStoreRhiConfig::Api api)
+{
+ switch (api) {
+ case QPlatformBackingStoreRhiConfig::OpenGL:
+ return QRhi::OpenGLES2;
+ case QPlatformBackingStoreRhiConfig::Metal:
+ return QRhi::Metal;
+ case QPlatformBackingStoreRhiConfig::Vulkan:
+ return QRhi::Vulkan;
+ case QPlatformBackingStoreRhiConfig::D3D11:
+ return QRhi::D3D11;
+ case QPlatformBackingStoreRhiConfig::Null:
+ return QRhi::Null;
+ default:
+ break;
+ }
+ return QRhi::Null;
+}
+
+const char *QBackingStoreRhiSupport::apiName(QPlatformBackingStoreRhiConfig::Api api)
+{
+ switch (api) {
+ case QPlatformBackingStoreRhiConfig::OpenGL:
+ return "OpenGL";
+ case QPlatformBackingStoreRhiConfig::Metal:
+ return "Metal";
+ case QPlatformBackingStoreRhiConfig::Vulkan:
+ return "Vulkan";
+ case QPlatformBackingStoreRhiConfig::D3D11:
+ return "D3D11";
+ case QPlatformBackingStoreRhiConfig::Null:
+ return "Null";
+ default:
+ break;
+ }
+ return "Unknown";
+}
+
+bool QBackingStoreRhiSupport::checkForceRhi(QPlatformBackingStoreRhiConfig *outConfig, QSurface::SurfaceType *outType)
+{
+ static QPlatformBackingStoreRhiConfig config;
+ static bool checked = false;
+
+ if (!checked) {
+ checked = true;
+
+ const bool alwaysRhi = qEnvironmentVariableIntValue("QT_WIDGETS_RHI");
+ if (alwaysRhi)
+ config.setEnabled(true);
+
+ // if enabled, choose an api
+ if (config.isEnabled()) {
+#if defined(Q_OS_WIN)
+ config.setApi(QPlatformBackingStoreRhiConfig::D3D11);
+#elif defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+ config.setApi(QPlatformBackingStoreRhiConfig::Metal);
+#elif QT_CONFIG(opengl)
+ config.setApi(QPlatformBackingStoreRhiConfig::OpenGL);
+#elif QT_CONFIG(vulkan)
+ config.setApi(QPlatformBackingStoreRhiConfig::Vulkan);
+#else
+ qWarning("QT_WIDGETS_RHI is set but no backend is available; ignoring");
+ return false;
+#endif
+
+ // the env.var. will always override
+ if (qEnvironmentVariableIsSet("QT_WIDGETS_RHI_BACKEND")) {
+ const QString backend = qEnvironmentVariable("QT_WIDGETS_RHI_BACKEND");
+#ifdef Q_OS_WIN
+ if (backend == QStringLiteral("d3d11") || backend == QStringLiteral("d3d"))
+ config.setApi(QPlatformBackingStoreRhiConfig::D3D11);
+#endif
+#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+ if (backend == QStringLiteral("metal"))
+ config.setApi(QPlatformBackingStoreRhiConfig::Metal);
+#endif
+#if QT_CONFIG(opengl)
+ if (backend == QStringLiteral("opengl") || backend == QStringLiteral("gl"))
+ config.setApi(QPlatformBackingStoreRhiConfig::OpenGL);
+#endif
+#if QT_CONFIG(vulkan)
+ if (backend == QStringLiteral("vulkan"))
+ config.setApi(QPlatformBackingStoreRhiConfig::Vulkan);
+#endif
+ }
+
+ if (qEnvironmentVariableIntValue("QT_WIDGETS_RHI_DEBUG_LAYER"))
+ config.setDebugLayer(true);
+ }
+
+ qCDebug(lcQpaBackingStore) << "Check for forced use of QRhi resulted in enable"
+ << config.isEnabled() << "with api" << apiName(config.api());
+ }
+
+ if (config.isEnabled()) {
+ if (outConfig)
+ *outConfig = config;
+ if (outType)
+ *outType = surfaceTypeForConfig(config);
+ return true;
+ }
+ return false;
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/painting/qbackingstorerhisupport_p.h b/src/gui/painting/qbackingstorerhisupport_p.h
new file mode 100644
index 0000000000..35a6bae1f2
--- /dev/null
+++ b/src/gui/painting/qbackingstorerhisupport_p.h
@@ -0,0 +1,114 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QBACKINGSTORERHISUPPORT_P_H
+#define QBACKINGSTORERHISUPPORT_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtGui/private/qtguiglobal_p.h>
+#include <QtGui/qwindow.h>
+#include <QtGui/qsurfaceformat.h>
+#include <QtGui/qoffscreensurface.h>
+#include <QtGui/private/qrhi_p.h>
+#include <qpa/qplatformbackingstore.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_GUI_EXPORT QBackingStoreRhiSupport
+{
+public:
+ ~QBackingStoreRhiSupport();
+
+ void reset();
+
+ void setFormat(const QSurfaceFormat &format) { m_format = format; }
+ void setWindow(QWindow *window) { m_window = window; }
+ void setConfig(const QPlatformBackingStoreRhiConfig &config) { m_config = config; }
+
+ bool create();
+
+ QRhiSwapChain *swapChainForWindow(QWindow *window);
+
+ static QSurface::SurfaceType surfaceTypeForConfig(const QPlatformBackingStoreRhiConfig &config);
+
+ static bool checkForceRhi(QPlatformBackingStoreRhiConfig *outConfig, QSurface::SurfaceType *outType);
+
+ static QRhi::Implementation apiToRhiBackend(QPlatformBackingStoreRhiConfig::Api api);
+ static const char *apiName(QPlatformBackingStoreRhiConfig::Api api);
+
+ QRhi *rhi() const { return m_rhi; }
+
+private:
+ QSurfaceFormat m_format;
+ QWindow *m_window = nullptr;
+ QPlatformBackingStoreRhiConfig m_config;
+ QRhi *m_rhi = nullptr;
+ QOffscreenSurface *m_openGLFallbackSurface = nullptr;
+ struct SwapchainData {
+ QRhiSwapChain *swapchain = nullptr;
+ QRhiRenderPassDescriptor *renderPassDescriptor = nullptr;
+ QObject *windowWatcher = nullptr;
+ void reset();
+ };
+ QHash<QWindow *, SwapchainData> m_swapchains;
+ friend class QBackingStoreRhiSupportWindowWatcher;
+};
+
+class QBackingStoreRhiSupportWindowWatcher : public QObject
+{
+public:
+ QBackingStoreRhiSupportWindowWatcher(QBackingStoreRhiSupport *rhiSupport) : m_rhiSupport(rhiSupport) { }
+ bool eventFilter(QObject *obj, QEvent *ev) override;
+private:
+ QBackingStoreRhiSupport *m_rhiSupport;
+};
+
+QT_END_NAMESPACE
+
+#endif // QBACKINGSTORERHISUPPORT_P_H
diff --git a/src/gui/painting/qplatformbackingstore.cpp b/src/gui/painting/qplatformbackingstore.cpp
index 3bd9746441..ab943c0658 100644
--- a/src/gui/painting/qplatformbackingstore.cpp
+++ b/src/gui/painting/qplatformbackingstore.cpp
@@ -40,6 +40,9 @@
#include "qplatformbackingstore.h"
#include <qwindow.h>
#include <qpixmap.h>
+#include <private/qbackingstorerhisupport_p.h>
+#include <private/qbackingstoredefaultcompositor_p.h>
+#include <private/qwindow_p.h>
#include <QtCore/private/qobject_p.h>
@@ -56,25 +59,20 @@ public:
{
}
- ~QPlatformBackingStorePrivate()
- {
-#ifndef QT_NO_OPENGL
- delete openGLSupport;
-#endif
- }
QWindow *window;
QBackingStore *backingStore;
-#ifndef QT_NO_OPENGL
- QPlatformBackingStoreOpenGLSupportBase *openGLSupport = nullptr;
-#endif
-};
-#ifndef QT_NO_OPENGL
+ // The order matters. if it needs to be rearranged in the future, call
+ // reset() explicitly from the dtor in the correct order.
+ // (first the compositor, then the rhiSupport)
+ QBackingStoreRhiSupport rhiSupport;
+ QBackingStoreDefaultCompositor compositor;
+};
struct QBackingstoreTextureInfo
{
void *source; // may be null
- GLuint textureId;
+ QRhiTexture *texture;
QRect rect;
QRect clipRect;
QPlatformTextureList::Flags flags;
@@ -109,10 +107,10 @@ int QPlatformTextureList::count() const
return d->textures.count();
}
-GLuint QPlatformTextureList::textureId(int index) const
+QRhiTexture *QPlatformTextureList::texture(int index) const
{
Q_D(const QPlatformTextureList);
- return d->textures.at(index).textureId;
+ return d->textures.at(index).texture;
}
void *QPlatformTextureList::source(int index)
@@ -154,13 +152,13 @@ bool QPlatformTextureList::isLocked() const
return d->locked;
}
-void QPlatformTextureList::appendTexture(void *source, GLuint textureId, const QRect &geometry,
+void QPlatformTextureList::appendTexture(void *source, QRhiTexture *texture, const QRect &geometry,
const QRect &clipRect, Flags flags)
{
Q_D(QPlatformTextureList);
QBackingstoreTextureInfo bi;
bi.source = source;
- bi.textureId = textureId;
+ bi.texture = texture;
bi.rect = geometry;
bi.clipRect = clipRect;
bi.flags = flags;
@@ -172,7 +170,6 @@ void QPlatformTextureList::clear()
Q_D(QPlatformTextureList);
d->textures.clear();
}
-#endif // QT_NO_OPENGL
/*!
\class QPlatformBackingStore
@@ -185,36 +182,54 @@ void QPlatformTextureList::clear()
windows.
*/
-#ifndef QT_NO_OPENGL
/*!
- Flushes the given \a region from the specified \a window onto the
- screen, and composes it with the specified \a textures.
-
- The default implementation retrieves the contents using toTexture()
- and composes using OpenGL. May be reimplemented in subclasses if there
- is a more efficient native way to do it.
+ Flushes the given \a region from the specified \a window.
\note \a region is relative to the window which may not be top-level in case
\a window corresponds to a native child widget. \a offset is the position of
the native child relative to the top-level window.
+
+ Unlike rhiFlush(), this function's default implementation does nothing. It
+ is expected that subclasses provide a platform-specific (non-QRhi-based)
+ implementation, if applicable on the given platform.
+
+ \sa rhiFlush()
*/
+void QPlatformBackingStore::flush(QWindow *window, const QRegion &region, const QPoint &offset)
+{
+ Q_UNUSED(window);
+ Q_UNUSED(region);
+ Q_UNUSED(offset);
+}
+
+/*!
+ Flushes the given \a region from the specified \a window, and compositing
+ it with the specified \a textures list.
-void QPlatformBackingStore::composeAndFlush(QWindow *window, const QRegion &region,
- const QPoint &offset,
- QPlatformTextureList *textures,
- bool translucentBackground)
+ The default implementation retrieves the contents using toTexture() and
+ composes using QRhi with OpenGL, Metal, Vulkan, or Direct 3D underneath.
+ May be reimplemented in subclasses if customization is desired.
+
+ \note \a region is relative to the window which may not be top-level in case
+ \a window corresponds to a native child widget. \a offset is the position of
+ the native child relative to the top-level window.
+
+ \sa flush()
+ */
+QPlatformBackingStore::FlushResult QPlatformBackingStore::rhiFlush(QWindow *window,
+ const QRegion &region,
+ const QPoint &offset,
+ QPlatformTextureList *textures,
+ bool translucentBackground)
{
- if (auto *c = d_ptr->openGLSupport)
- c->composeAndFlush(window, region, offset, textures, translucentBackground);
- else
- qWarning() << Q_FUNC_INFO << "no opengl support set";
+ return d_ptr->compositor.flush(this, d_ptr->rhiSupport.rhi(), d_ptr->rhiSupport.swapChainForWindow(window),
+ window, region, offset, textures, translucentBackground);
}
-#endif
/*!
Implemented in subclasses to return the content of the backingstore as a QImage.
- If QPlatformIntegration::RasterGLSurface is supported, either this function or
+ If composition via a 3D graphics API is supported, either this function or
toTexture() must be implemented.
The returned image is only valid until the next operation (resize, paint, scroll,
@@ -228,22 +243,19 @@ QImage QPlatformBackingStore::toImage() const
return QImage();
}
-#ifndef QT_NO_OPENGL
/*!
May be reimplemented in subclasses to return the content of the
- backingstore as an OpenGL texture. \a dirtyRegion is the part of the
+ backingstore as an QRhiTexture. \a dirtyRegion is the part of the
backingstore which may have changed since the last call to this function. The
caller of this function must ensure that there is a current context.
- The size of the texture is returned in \a textureSize.
-
The ownership of the texture is not transferred. The caller must not store
the return value between calls, but instead call this function before each use.
- The default implementation returns a cached texture if \a dirtyRegion is empty and
- \a textureSize matches the backingstore size, otherwise it retrieves the content using
- toImage() and performs a texture upload. This works only if the value of \a textureSize
- is preserved between the calls to this function.
+ The default implementation returns a cached texture if \a dirtyRegion is
+ empty and the existing texture's size matches the backingstore size,
+ otherwise it retrieves the content using toImage() and performs a texture
+ upload.
If the red and blue components have to swapped, \a flags will be set to include \c
TextureSwizzle. This allows creating textures from images in formats like
@@ -257,16 +269,12 @@ QImage QPlatformBackingStore::toImage() const
\note \a dirtyRegion is relative to the backingstore so no adjustment is needed.
*/
-GLuint QPlatformBackingStore::toTexture(const QRegion &dirtyRegion, QSize *textureSize, TextureFlags *flags) const
+QRhiTexture *QPlatformBackingStore::toTexture(QRhiResourceUpdateBatch *resourceUpdates,
+ const QRegion &dirtyRegion,
+ TextureFlags *flags) const
{
- if (auto *c = d_ptr->openGLSupport)
- return c->toTexture(dirtyRegion, textureSize, flags);
- else {
- qWarning() << Q_FUNC_INFO << "no opengl support set";
- return 0;
- }
+ return d_ptr->compositor.toTexture(this, d_ptr->rhiSupport.rhi(), resourceUpdates, dirtyRegion, flags);
}
-#endif // QT_NO_OPENGL
/*!
\fn QPaintDevice* QPlatformBackingStore::paintDevice()
@@ -280,12 +288,6 @@ GLuint QPlatformBackingStore::toTexture(const QRegion &dirtyRegion, QSize *textu
QPlatformBackingStore::QPlatformBackingStore(QWindow *window)
: d_ptr(new QPlatformBackingStorePrivate(window))
{
-#ifndef QT_NO_OPENGL
- if (auto createOpenGLSupport = QPlatformBackingStoreOpenGLSupportBase::factoryFunction()) {
- d_ptr->openGLSupport = createOpenGLSupport();
- d_ptr->openGLSupport->backingStore = this;
- }
-#endif
}
/*!
@@ -322,30 +324,6 @@ QBackingStore *QPlatformBackingStore::backingStore() const
return d_ptr->backingStore;
}
-#ifndef QT_NO_OPENGL
-
-using FactoryFunction = QPlatformBackingStoreOpenGLSupportBase::FactoryFunction;
-
-/*!
- Registers a factory function for OpenGL implementation helper.
-
- The QtOpenGL library automatically registers a default function,
- unless already set by the platform plugin in other ways.
-*/
-void QPlatformBackingStoreOpenGLSupportBase::setFactoryFunction(FactoryFunction function)
-{
- s_factoryFunction = function;
-}
-
-FactoryFunction QPlatformBackingStoreOpenGLSupportBase::factoryFunction()
-{
- return s_factoryFunction;
-}
-
-FactoryFunction QPlatformBackingStoreOpenGLSupportBase::s_factoryFunction = nullptr;
-
-#endif // QT_NO_OPENGL
-
/*!
This function is called before painting onto the surface begins,
with the \a region in which the painting will occur.
@@ -390,4 +368,33 @@ bool QPlatformBackingStore::scroll(const QRegion &area, int dx, int dy)
return false;
}
+void QPlatformBackingStore::setRhiConfig(const QPlatformBackingStoreRhiConfig &config)
+{
+ if (!config.isEnabled())
+ return;
+
+ d_ptr->rhiSupport.setConfig(config);
+ d_ptr->rhiSupport.setWindow(d_ptr->window);
+ d_ptr->rhiSupport.setFormat(d_ptr->window->format());
+ d_ptr->rhiSupport.create();
+}
+
+QRhi *QPlatformBackingStore::rhi() const
+{
+ // Returning null is valid, and means this is not a QRhi-capable backingstore.
+ return d_ptr->rhiSupport.rhi();
+}
+
+void QPlatformBackingStore::graphicsDeviceReportedLost()
+{
+ if (!d_ptr->rhiSupport.rhi())
+ return;
+
+ qWarning("Rhi backingstore: graphics device lost, attempting to reinitialize");
+ d_ptr->rhiSupport.reset();
+ d_ptr->rhiSupport.create();
+ if (!d_ptr->rhiSupport.rhi())
+ qWarning("Rhi backingstore: failed to reinitialize after losing the device");
+}
+
QT_END_NAMESPACE
diff --git a/src/gui/painting/qplatformbackingstore.h b/src/gui/painting/qplatformbackingstore.h
index e0fdca2f21..97df8fca4f 100644
--- a/src/gui/painting/qplatformbackingstore.h
+++ b/src/gui/painting/qplatformbackingstore.h
@@ -56,7 +56,6 @@
#include <QtGui/qwindow.h>
#include <QtGui/qregion.h>
-#include <QtGui/qopengl.h>
QT_BEGIN_NAMESPACE
@@ -70,9 +69,73 @@ class QPlatformBackingStorePrivate;
class QPlatformTextureList;
class QPlatformTextureListPrivate;
class QPlatformGraphicsBuffer;
-class QPlatformBackingStoreOpenGLSupportBase;
+class QRhi;
+class QRhiTexture;
+class QRhiResourceUpdateBatch;
+class QRhiSwapChain;
+
+struct Q_GUI_EXPORT QPlatformBackingStoreRhiConfig
+{
+ enum Api {
+ OpenGL,
+ Metal,
+ Vulkan,
+ D3D11,
+ Null
+ };
+
+ QPlatformBackingStoreRhiConfig()
+ : m_enable(false)
+ { }
+
+ QPlatformBackingStoreRhiConfig(Api api)
+ : m_enable(true),
+ m_api(api)
+ { }
+
+ bool isEnabled() const { return m_enable; }
+ void setEnabled(bool enable) { m_enable = enable; }
+
+ Api api() const { return m_api; }
+ void setApi(Api api) { m_api = api; }
+
+ bool isDebugLayerEnabled() const { return m_debugLayer; }
+ void setDebugLayer(bool enable) { m_debugLayer = enable; }
+
+ QByteArrayList instanceExtensions() const { return m_instanceExtensions; }
+ void setInstanceExtensions(const QByteArrayList &e) { m_instanceExtensions = e; }
+
+ QByteArrayList instanceLayers() const { return m_instanceLayers; }
+ void setInstanceLayers(const QByteArrayList &e) { m_instanceLayers = e; }
+
+ QByteArrayList deviceExtensions() const { return m_deviceExtensions; }
+ void setDeviceExtensions(const QByteArrayList &e) { m_deviceExtensions = e; }
+
+private:
+ bool m_enable;
+ Api m_api = Null;
+ bool m_debugLayer = false;
+ QByteArrayList m_instanceExtensions;
+ QByteArrayList m_instanceLayers;
+ QByteArrayList m_deviceExtensions;
+ friend bool operator==(const QPlatformBackingStoreRhiConfig &a, const QPlatformBackingStoreRhiConfig &b);
+};
+
+inline bool operator==(const QPlatformBackingStoreRhiConfig &a, const QPlatformBackingStoreRhiConfig &b)
+{
+ return a.m_enable == b.m_enable
+ && a.m_api == b.m_api
+ && a.m_debugLayer == b.m_debugLayer
+ && a.m_instanceExtensions == b.m_instanceExtensions
+ && a.m_instanceLayers == b.m_instanceLayers
+ && a.m_deviceExtensions == b.m_deviceExtensions;
+}
+
+inline bool operator!=(const QPlatformBackingStoreRhiConfig &a, const QPlatformBackingStoreRhiConfig &b)
+{
+ return !(a == b);
+}
-#ifndef QT_NO_OPENGL
class Q_GUI_EXPORT QPlatformTextureList : public QObject
{
Q_OBJECT
@@ -90,7 +153,7 @@ public:
int count() const;
bool isEmpty() const { return count() == 0; }
- GLuint textureId(int index) const;
+ QRhiTexture *texture(int index) const;
QRect geometry(int index) const;
QRect clipRect(int index) const;
void *source(int index);
@@ -98,7 +161,7 @@ public:
void lock(bool on);
bool isLocked() const;
- void appendTexture(void *source, GLuint textureId, const QRect &geometry,
+ void appendTexture(void *source, QRhiTexture *texture, const QRect &geometry,
const QRect &clipRect = QRect(), Flags flags = { });
void clear();
@@ -106,11 +169,16 @@ public:
void locked(bool);
};
Q_DECLARE_OPERATORS_FOR_FLAGS(QPlatformTextureList::Flags)
-#endif
class Q_GUI_EXPORT QPlatformBackingStore
{
public:
+ enum FlushResult {
+ FlushSuccess,
+ FlushFailed,
+ FlushFailedDueToLostDevice
+ };
+
explicit QPlatformBackingStore(QWindow *window);
virtual ~QPlatformBackingStore();
@@ -119,22 +187,25 @@ public:
virtual QPaintDevice *paintDevice() = 0;
- virtual void flush(QWindow *window, const QRegion &region, const QPoint &offset) = 0;
-#ifndef QT_NO_OPENGL
- virtual void composeAndFlush(QWindow *window, const QRegion &region, const QPoint &offset,
+ virtual void flush(QWindow *window, const QRegion &region, const QPoint &offset);
+
+ virtual FlushResult rhiFlush(QWindow *window,
+ const QRegion &region,
+ const QPoint &offset,
QPlatformTextureList *textures,
bool translucentBackground);
-#endif
+
virtual QImage toImage() const;
-#ifndef QT_NO_OPENGL
+
enum TextureFlag {
TextureSwizzle = 0x01,
TextureFlip = 0x02,
- TexturePremultiplied = 0x04,
+ TexturePremultiplied = 0x04
};
Q_DECLARE_FLAGS(TextureFlags, TextureFlag)
- virtual GLuint toTexture(const QRegion &dirtyRegion, QSize *textureSize, TextureFlags *flags) const;
-#endif
+ virtual QRhiTexture *toTexture(QRhiResourceUpdateBatch *resourceUpdates,
+ const QRegion &dirtyRegion,
+ TextureFlags *flags) const;
virtual QPlatformGraphicsBuffer *graphicsBuffer() const;
@@ -145,6 +216,12 @@ public:
virtual void beginPaint(const QRegion &);
virtual void endPaint();
+ void setRhiConfig(const QPlatformBackingStoreRhiConfig &config);
+ QRhi *rhi() const;
+ QRhiSwapChain *rhiSwapChain() const;
+ void surfaceAboutToBeDestroyed();
+ void graphicsDeviceReportedLost();
+
private:
QPlatformBackingStorePrivate *d_ptr;
@@ -152,31 +229,7 @@ private:
friend class QBackingStore;
};
-#ifndef QT_NO_OPENGL
-class Q_GUI_EXPORT QPlatformBackingStoreOpenGLSupportBase
-{
-public:
- virtual void composeAndFlush(QWindow *window, const QRegion &region, const QPoint &offset,
- QPlatformTextureList *textures, bool translucentBackground) = 0;
- virtual GLuint toTexture(const QRegion &dirtyRegion, QSize *textureSize, QPlatformBackingStore::TextureFlags *flags) const = 0;
- virtual ~QPlatformBackingStoreOpenGLSupportBase() {}
-
- using FactoryFunction = QPlatformBackingStoreOpenGLSupportBase *(*)();
- static void setFactoryFunction(FactoryFunction);
- static FactoryFunction factoryFunction();
-
-protected:
- QPlatformBackingStore *backingStore = nullptr;
- friend class QPlatformBackingStore;
-
-private:
- static FactoryFunction s_factoryFunction;
-};
-#endif // QT_NO_OPENGL
-
-#ifndef QT_NO_OPENGL
Q_DECLARE_OPERATORS_FOR_FLAGS(QPlatformBackingStore::TextureFlags)
-#endif
QT_END_NAMESPACE
diff --git a/src/opengl/qplatformbackingstoreopenglsupport.h b/src/gui/painting/qrhibackingstore.cpp
index ae9d972c6a..b74fdca8de 100644
--- a/src/opengl/qplatformbackingstoreopenglsupport.h
+++ b/src/gui/painting/qrhibackingstore.cpp
@@ -1,9 +1,9 @@
/****************************************************************************
**
-** Copyright (C) 2020 The Qt Company Ltd.
+** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
-** This file is part of the QtGui module of the Qt Toolkit.
+** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
@@ -37,51 +37,46 @@
**
****************************************************************************/
-#ifndef QPLATFORMBACKINGSTOREOPENGLSUPPORT_H
-#define QPLATFORMBACKINGSTOREOPENGLSUPPORT_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is part of the QPA API and is not meant to be used
-// in applications. Usage of this API may make your code
-// source and binary incompatible with future versions of Qt.
-//
-
-#ifndef QT_NO_OPENGL
-
-#include <QtOpenGL/qtopenglglobal.h>
-#include <qpa/qplatformbackingstore.h>
-
-#include <QtGui/QOpenGLContext>
+#include "qrhibackingstore_p.h"
QT_BEGIN_NAMESPACE
-class QOpenGLTextureBlitter;
-class QOpenGLBackingStore;
+QRhiBackingStore::QRhiBackingStore(QWindow *window)
+ : QRasterBackingStore(window)
+{
+}
-class Q_OPENGL_EXPORT QPlatformBackingStoreOpenGLSupport : public QPlatformBackingStoreOpenGLSupportBase
+QRhiBackingStore::~QRhiBackingStore()
{
-public:
- ~QPlatformBackingStoreOpenGLSupport() override;
- void composeAndFlush(QWindow *window, const QRegion &region, const QPoint &offset,
- QPlatformTextureList *textures, bool translucentBackground) override;
- GLuint toTexture(const QRegion &dirtyRegion, QSize *textureSize, QPlatformBackingStore::TextureFlags *flags) const override;
+}
-private:
- QScopedPointer<QOpenGLContext> context;
- mutable GLuint textureId = 0;
- mutable QSize textureSize;
- mutable bool needsSwizzle = false;
- mutable bool premultiplied = false;
- QOpenGLTextureBlitter *blitter = nullptr;
-};
+void QRhiBackingStore::flush(QWindow *window, const QRegion &region, const QPoint &offset)
+{
+ Q_UNUSED(region);
+ Q_UNUSED(offset);
-Q_OPENGL_EXPORT void qt_registerDefaultPlatformBackingStoreOpenGLSupport();
+ if (window != this->window())
+ return;
-QT_END_NAMESPACE
+ if (!rhi()) {
+ QPlatformBackingStoreRhiConfig rhiConfig;
+ switch (window->surfaceType()) {
+ case QSurface::OpenGLSurface:
+ rhiConfig.setApi(QPlatformBackingStoreRhiConfig::OpenGL);
+ break;
+ case QSurface::MetalSurface:
+ rhiConfig.setApi(QPlatformBackingStoreRhiConfig::Metal);
+ break;
+ default:
+ Q_UNREACHABLE();
+ }
+ rhiConfig.setEnabled(true);
+ setRhiConfig(rhiConfig);
+ }
-#endif // QT_NO_OPENGL
+ static QPlatformTextureList emptyTextureList;
+ bool translucentBackground = m_image.hasAlphaChannel();
+ rhiFlush(window, region, offset, &emptyTextureList, translucentBackground);
+}
-#endif // QPLATFORMBACKINGSTOREOPENGLSUPPORT_H
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/ios/qiosbackingstore.h b/src/gui/painting/qrhibackingstore_p.h
index a32a6d2eed..6889840a98 100644
--- a/src/plugins/platforms/ios/qiosbackingstore.h
+++ b/src/gui/painting/qrhibackingstore_p.h
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
@@ -37,26 +37,33 @@
**
****************************************************************************/
-#ifndef QIOSBACKINGSTORE_H
-#define QIOSBACKINGSTORE_H
+#ifndef QRHIBACKINGSTORE_H
+#define QRHIBACKINGSTORE_H
-#include <qpa/qplatformbackingstore.h>
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
#include <QtGui/private/qrasterbackingstore_p.h>
QT_BEGIN_NAMESPACE
-class QOpenGLPaintDevice;
-
-class QIOSBackingStore : public QRasterBackingStore
+class Q_GUI_EXPORT QRhiBackingStore : public QRasterBackingStore
{
public:
- QIOSBackingStore(QWindow *window);
- ~QIOSBackingStore();
+ QRhiBackingStore(QWindow *window);
+ ~QRhiBackingStore();
void flush(QWindow *window, const QRegion &region, const QPoint &offset) override;
};
QT_END_NAMESPACE
-#endif // QIOSBACKINGSTORE_H
+#endif // QRHIBACKINGSTORE_H
diff --git a/src/gui/painting/shaders/backingstorecompose.frag b/src/gui/painting/shaders/backingstorecompose.frag
new file mode 100644
index 0000000000..e0c419840e
--- /dev/null
+++ b/src/gui/painting/shaders/backingstorecompose.frag
@@ -0,0 +1,23 @@
+#version 440
+
+layout(location = 0) in vec2 v_texcoord;
+layout(location = 0) out vec4 fragColor;
+
+layout(std140, binding = 0) uniform buf {
+ mat4 vertexTransform;
+ mat3 textureTransform;
+ float opacity;
+ int swapRedBlue;
+};
+
+layout(binding = 1) uniform sampler2D textureSampler;
+
+void main()
+{
+ vec4 tmpFragColor = texture(textureSampler, v_texcoord);
+ tmpFragColor.a *= opacity;
+ if (swapRedBlue == 0)
+ fragColor = tmpFragColor;
+ else
+ fragColor = tmpFragColor.bgra;
+}
diff --git a/src/gui/painting/shaders/backingstorecompose.frag.qsb b/src/gui/painting/shaders/backingstorecompose.frag.qsb
new file mode 100644
index 0000000000..a7c2dfebce
--- /dev/null
+++ b/src/gui/painting/shaders/backingstorecompose.frag.qsb
Binary files differ
diff --git a/src/gui/painting/shaders/backingstorecompose.vert b/src/gui/painting/shaders/backingstorecompose.vert
new file mode 100644
index 0000000000..bb8444ade5
--- /dev/null
+++ b/src/gui/painting/shaders/backingstorecompose.vert
@@ -0,0 +1,19 @@
+#version 440
+
+layout(location = 0) in vec3 position;
+layout(location = 1) in vec2 texcoord;
+
+layout(location = 0) out vec2 v_texcoord;
+
+layout(std140, binding = 0) uniform buf {
+ mat4 vertexTransform;
+ mat3 textureTransform;
+ float opacity;
+ int swapRedBlue;
+};
+
+void main()
+{
+ v_texcoord = (textureTransform * vec3(texcoord, 1.0)).xy;
+ gl_Position = vertexTransform * vec4(position, 1.0);
+}
diff --git a/src/gui/painting/shaders/backingstorecompose.vert.qsb b/src/gui/painting/shaders/backingstorecompose.vert.qsb
new file mode 100644
index 0000000000..1f464516fe
--- /dev/null
+++ b/src/gui/painting/shaders/backingstorecompose.vert.qsb
Binary files differ
diff --git a/src/opengl/CMakeLists.txt b/src/opengl/CMakeLists.txt
index 03ace0cad7..f9eb5e36a3 100644
--- a/src/opengl/CMakeLists.txt
+++ b/src/opengl/CMakeLists.txt
@@ -30,7 +30,6 @@ qt_internal_add_module(OpenGL
qopenglversionprofile.cpp qopenglversionprofile.h
qopenglvertexarrayobject.cpp qopenglvertexarrayobject.h
qopenglwindow.cpp qopenglwindow.h
- qplatformbackingstoreopenglsupport.cpp qplatformbackingstoreopenglsupport.h
qtopenglglobal.h
DEFINES
QT_NO_FOREACH
diff --git a/src/opengl/doc/snippets/code/doc_gui_widgets_qopenglwidget.cpp b/src/opengl/doc/snippets/code/doc_gui_widgets_qopenglwidget.cpp
index 633a413b4f..c1bf6731c7 100644
--- a/src/opengl/doc/snippets/code/doc_gui_widgets_qopenglwidget.cpp
+++ b/src/opengl/doc/snippets/code/doc_gui_widgets_qopenglwidget.cpp
@@ -173,9 +173,14 @@ void MyGLWidget::initializeGL()
//! [4]
//! [5]
+MyGLWidget::~MyGLWidget()
+{
+ cleanup();
+}
+
void MyGLWidget::initializeGL()
{
- // context() and QOpenGLContext::currentContext() are equivalent when called from initializeGL or paintGL.
+ ...
connect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &MyGLWidget::cleanup);
}
@@ -186,6 +191,7 @@ void MyGLWidget::cleanup()
m_texture = 0;
...
doneCurrent();
+ disconnect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &MyGLWidget::cleanup);
}
//! [5]
diff --git a/src/opengl/qopenglcompositor.cpp b/src/opengl/qopenglcompositor.cpp
index abfaca3f9c..a5fe4dda30 100644
--- a/src/opengl/qopenglcompositor.cpp
+++ b/src/opengl/qopenglcompositor.cpp
@@ -40,6 +40,7 @@
#include <QtOpenGL/QOpenGLFramebufferObject>
#include <QtGui/QOpenGLContext>
#include <QtGui/QWindow>
+#include <QtGui/private/qrhi_p.h>
#include <qpa/qplatformbackingstore.h>
#include "qopenglcompositor_p.h"
@@ -207,7 +208,8 @@ static void clippedBlit(const QPlatformTextureList *textures, int idx, const QRe
const QMatrix3x3 source = QOpenGLTextureBlitter::sourceTransform(srcRect, rectInWindow.size(),
QOpenGLTextureBlitter::OriginBottomLeft);
- blitter->blit(textures->textureId(idx), target, source);
+ const uint textureId = textures->texture(idx)->nativeTexture().object;
+ blitter->blit(textureId, target, source);
}
void QOpenGLCompositor::render(QOpenGLCompositorWindow *window)
@@ -221,7 +223,7 @@ void QOpenGLCompositor::render(QOpenGLCompositorWindow *window)
BlendStateBinder blend;
const QRect sourceWindowRect = window->sourceWindow()->geometry();
for (int i = 0; i < textures->count(); ++i) {
- uint textureId = textures->textureId(i);
+ const uint textureId = textures->texture(i)->nativeTexture().object;
const float opacity = window->sourceWindow()->opacity();
if (opacity != currentOpacity) {
currentOpacity = opacity;
diff --git a/src/opengl/qopenglcompositorbackingstore.cpp b/src/opengl/qopenglcompositorbackingstore.cpp
index 34542dde47..ed4a0ffb3c 100644
--- a/src/opengl/qopenglcompositorbackingstore.cpp
+++ b/src/opengl/qopenglcompositorbackingstore.cpp
@@ -43,6 +43,7 @@
#include <QtGui/QOffscreenSurface>
#include <qpa/qplatformbackingstore.h>
#include <private/qwindow_p.h>
+#include <private/qrhi_p.h>
#include "qopenglcompositorbackingstore_p.h"
#include "qopenglcompositor_p.h"
@@ -79,9 +80,11 @@ QOpenGLCompositorBackingStore::QOpenGLCompositorBackingStore(QWindow *window)
: QPlatformBackingStore(window),
m_window(window),
m_bsTexture(0),
+ m_bsTextureWrapper(nullptr),
m_bsTextureContext(0),
m_textures(new QPlatformTextureList),
- m_lockedWidgetTextures(0)
+ m_lockedWidgetTextures(0),
+ m_rhi(nullptr)
{
}
@@ -103,10 +106,12 @@ QOpenGLCompositorBackingStore::~QOpenGLCompositorBackingStore()
}
}
- if (m_bsTextureContext && ctx && ctx->shareGroup() == m_bsTextureContext->shareGroup())
+ if (m_bsTextureContext && ctx && ctx->shareGroup() == m_bsTextureContext->shareGroup()) {
+ delete m_bsTextureWrapper;
glDeleteTextures(1, &m_bsTexture);
- else
+ } else {
qWarning("QOpenGLCompositorBackingStore: Texture is not valid in the current context");
+ }
if (tempSurface && ctx)
ctx->doneCurrent();
@@ -132,6 +137,8 @@ void QOpenGLCompositorBackingStore::updateTexture()
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_image.width(), m_image.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+ m_bsTextureWrapper = m_rhi->newTexture(QRhiTexture::RGBA8, m_image.size());
+ m_bsTextureWrapper->createFrom({m_bsTexture, 0});
} else {
glBindTexture(GL_TEXTURE_2D, m_bsTexture);
}
@@ -198,14 +205,16 @@ void QOpenGLCompositorBackingStore::flush(QWindow *window, const QRegion &region
dstCtx->makeCurrent(dstWin);
updateTexture();
m_textures->clear();
- m_textures->appendTexture(nullptr, m_bsTexture, window->geometry());
+ m_textures->appendTexture(nullptr, m_bsTextureWrapper, window->geometry());
compositor->update();
}
-void QOpenGLCompositorBackingStore::composeAndFlush(QWindow *window, const QRegion &region, const QPoint &offset,
- QPlatformTextureList *textures,
- bool translucentBackground)
+QPlatformBackingStore::FlushResult QOpenGLCompositorBackingStore::rhiFlush(QWindow *window,
+ const QRegion &region,
+ const QPoint &offset,
+ QPlatformTextureList *textures,
+ bool translucentBackground)
{
// QOpenGLWidget/QQuickWidget content provided as textures. The raster content goes on top.
@@ -213,34 +222,35 @@ void QOpenGLCompositorBackingStore::composeAndFlush(QWindow *window, const QRegi
Q_UNUSED(offset);
Q_UNUSED(translucentBackground);
+ m_rhi = rhi();
+
QOpenGLCompositor *compositor = QOpenGLCompositor::instance();
QOpenGLContext *dstCtx = compositor->context();
Q_ASSERT(dstCtx); // setTarget() must have been called before, e.g. from QEGLFSWindow
- // The compositor's context and the context to which QOpenGLWidget/QQuickWidget
- // textures belong are not the same. They share resources, though.
- Q_ASSERT(qt_window_private(window)->shareContext()->shareGroup() == dstCtx->shareGroup());
-
QWindow *dstWin = compositor->targetWindow();
if (!dstWin)
- return;
+ return FlushFailed;
dstCtx->makeCurrent(dstWin);
QWindowPrivate::get(window)->lastComposeTime.start();
m_textures->clear();
- for (int i = 0; i < textures->count(); ++i)
- m_textures->appendTexture(textures->source(i), textures->textureId(i), textures->geometry(i),
+ for (int i = 0; i < textures->count(); ++i) {
+ m_textures->appendTexture(textures->source(i), textures->texture(i), textures->geometry(i),
textures->clipRect(i), textures->flags(i));
+ }
updateTexture();
- m_textures->appendTexture(nullptr, m_bsTexture, window->geometry());
+ m_textures->appendTexture(nullptr, m_bsTextureWrapper, window->geometry());
textures->lock(true);
m_lockedWidgetTextures = textures;
compositor->update();
+
+ return FlushSuccess;
}
void QOpenGLCompositorBackingStore::notifyComposited()
@@ -280,6 +290,8 @@ void QOpenGLCompositorBackingStore::resize(const QSize &size, const QRegion &sta
dstCtx->makeCurrent(dstWin);
if (m_bsTexture) {
+ delete m_bsTextureWrapper;
+ m_bsTextureWrapper = nullptr;
glDeleteTextures(1, &m_bsTexture);
m_bsTexture = 0;
m_bsTextureContext = nullptr;
diff --git a/src/opengl/qopenglcompositorbackingstore_p.h b/src/opengl/qopenglcompositorbackingstore_p.h
index 6c61ab1bff..e8be9eef9e 100644
--- a/src/opengl/qopenglcompositorbackingstore_p.h
+++ b/src/opengl/qopenglcompositorbackingstore_p.h
@@ -62,6 +62,7 @@ QT_BEGIN_NAMESPACE
class QOpenGLContext;
class QPlatformTextureList;
+class QRhiTexture;
class Q_OPENGL_EXPORT QOpenGLCompositorBackingStore : public QPlatformBackingStore
{
@@ -77,7 +78,10 @@ public:
void resize(const QSize &size, const QRegion &staticContents) override;
QImage toImage() const override;
- void composeAndFlush(QWindow *window, const QRegion &region, const QPoint &offset,
+
+ FlushResult rhiFlush(QWindow *window,
+ const QRegion &region,
+ const QPoint &offset,
QPlatformTextureList *textures,
bool translucentBackground) override;
@@ -92,9 +96,11 @@ private:
QImage m_image;
QRegion m_dirty;
uint m_bsTexture;
+ QRhiTexture *m_bsTextureWrapper;
QOpenGLContext *m_bsTextureContext;
QPlatformTextureList *m_textures;
QPlatformTextureList *m_lockedWidgetTextures;
+ QRhi *m_rhi;
};
QT_END_NAMESPACE
diff --git a/src/opengl/qplatformbackingstoreopenglsupport.cpp b/src/opengl/qplatformbackingstoreopenglsupport.cpp
deleted file mode 100644
index 30532c5b4b..0000000000
--- a/src/opengl/qplatformbackingstoreopenglsupport.cpp
+++ /dev/null
@@ -1,465 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2020 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtGui module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** 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 Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** 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-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#ifndef QT_NO_OPENGL
-
-#include "qplatformbackingstoreopenglsupport.h"
-
-#include <QtGui/private/qwindow_p.h>
-
-#include <qpa/qplatformgraphicsbuffer.h>
-#include <qpa/qplatformgraphicsbufferhelper.h>
-
-#include <QtOpenGL/QOpenGLTextureBlitter>
-#include <QtGui/qopengl.h>
-#include <QtGui/QOpenGLFunctions>
-#include <QtGui/QOpenGLContext>
-#include <QtGui/QOffscreenSurface>
-
-#ifndef GL_TEXTURE_BASE_LEVEL
-#define GL_TEXTURE_BASE_LEVEL 0x813C
-#endif
-#ifndef GL_TEXTURE_MAX_LEVEL
-#define GL_TEXTURE_MAX_LEVEL 0x813D
-#endif
-#ifndef GL_UNPACK_ROW_LENGTH
-#define GL_UNPACK_ROW_LENGTH 0x0CF2
-#endif
-#ifndef GL_RGB10_A2
-#define GL_RGB10_A2 0x8059
-#endif
-#ifndef GL_UNSIGNED_INT_2_10_10_10_REV
-#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368
-#endif
-
-#ifndef GL_FRAMEBUFFER_SRGB
-#define GL_FRAMEBUFFER_SRGB 0x8DB9
-#endif
-#ifndef GL_FRAMEBUFFER_SRGB_CAPABLE
-#define GL_FRAMEBUFFER_SRGB_CAPABLE 0x8DBA
-#endif
-
-QT_BEGIN_NAMESPACE
-
-static inline QRect deviceRect(const QRect &rect, QWindow *window)
-{
- QRect deviceRect(rect.topLeft() * window->devicePixelRatio(),
- rect.size() * window->devicePixelRatio());
- return deviceRect;
-}
-
-static inline QPoint deviceOffset(const QPoint &pt, QWindow *window)
-{
- return pt * window->devicePixelRatio();
-}
-
-static QRegion deviceRegion(const QRegion &region, QWindow *window, const QPoint &offset)
-{
- if (offset.isNull() && window->devicePixelRatio() <= 1)
- return region;
-
- QVarLengthArray<QRect, 4> rects;
- rects.reserve(region.rectCount());
- for (const QRect &rect : region)
- rects.append(deviceRect(rect.translated(offset), window));
-
- QRegion deviceRegion;
- deviceRegion.setRects(rects.constData(), rects.count());
- return deviceRegion;
-}
-
-static inline QRect toBottomLeftRect(const QRect &topLeftRect, int windowHeight)
-{
- return QRect(topLeftRect.x(), windowHeight - topLeftRect.bottomRight().y() - 1,
- topLeftRect.width(), topLeftRect.height());
-}
-
-static void blitTextureForWidget(const QPlatformTextureList *textures, int idx, QWindow *window, const QRect &deviceWindowRect,
- QOpenGLTextureBlitter *blitter, const QPoint &offset, bool canUseSrgb)
-{
- const QRect clipRect = textures->clipRect(idx);
- if (clipRect.isEmpty())
- return;
-
- QRect rectInWindow = textures->geometry(idx);
- // relative to the TLW, not necessarily our window (if the flush is for a native child widget), have to adjust
- rectInWindow.translate(-offset);
-
- const QRect clippedRectInWindow = rectInWindow & clipRect.translated(rectInWindow.topLeft());
- const QRect srcRect = toBottomLeftRect(clipRect, rectInWindow.height());
-
- const QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(deviceRect(clippedRectInWindow, window),
- deviceWindowRect);
-
- const QMatrix3x3 source = QOpenGLTextureBlitter::sourceTransform(deviceRect(srcRect, window),
- deviceRect(rectInWindow, window).size(),
- QOpenGLTextureBlitter::OriginBottomLeft);
-
- QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions();
- const bool srgb = textures->flags(idx).testFlag(QPlatformTextureList::TextureIsSrgb);
- if (srgb && canUseSrgb)
- funcs->glEnable(GL_FRAMEBUFFER_SRGB);
-
- blitter->blit(textures->textureId(idx), target, source);
-
- if (srgb && canUseSrgb)
- funcs->glDisable(GL_FRAMEBUFFER_SRGB);
-}
-
-QPlatformBackingStoreOpenGLSupport::~QPlatformBackingStoreOpenGLSupport() {
- if (context) {
- QOffscreenSurface offscreenSurface;
- offscreenSurface.setFormat(context->format());
- offscreenSurface.create();
- context->makeCurrent(&offscreenSurface);
- if (textureId)
- context->functions()->glDeleteTextures(1, &textureId);
- if (blitter)
- blitter->destroy();
- }
- delete blitter;
-}
-
-void QPlatformBackingStoreOpenGLSupport::composeAndFlush(QWindow *window, const QRegion &region, const QPoint &offset, QPlatformTextureList *textures, bool translucentBackground)
-{
- if (!qt_window_private(window)->receivedExpose)
- return;
-
- if (!context) {
- context.reset(new QOpenGLContext);
- context->setFormat(window->requestedFormat());
- context->setScreen(window->screen());
- context->setShareContext(qt_window_private(window)->shareContext());
- if (!context->create()) {
- qCWarning(lcQpaBackingStore, "composeAndFlush: QOpenGLContext creation failed");
- return;
- }
- }
-
- bool current = context->makeCurrent(window);
-
- if (!current && !context->isValid()) {
- // release resources and attempt to reinitialize upon context loss
- delete blitter;
- blitter = nullptr;
- textureId = 0;
- current = context->create() && context->makeCurrent(window);
- }
-
- if (!current) {
- qCWarning(lcQpaBackingStore, "composeAndFlush: makeCurrent() failed");
- return;
- }
-
- qCDebug(lcQpaBackingStore) << "Composing and flushing" << region << "of" << window
- << "at offset" << offset << "with" << textures->count() << "texture(s) in" << textures;
-
- QWindowPrivate::get(window)->lastComposeTime.start();
-
- QOpenGLFunctions *funcs = context->functions();
- funcs->glViewport(0, 0, qRound(window->width() * window->devicePixelRatio()), qRound(window->height() * window->devicePixelRatio()));
- funcs->glClearColor(0, 0, 0, translucentBackground ? 0 : 1);
- funcs->glClear(GL_COLOR_BUFFER_BIT);
-
- if (!blitter) {
- blitter = new QOpenGLTextureBlitter;
- blitter->create();
- }
-
- blitter->bind();
-
- const QRect deviceWindowRect = deviceRect(QRect(QPoint(), window->size()), window);
- const QPoint deviceWindowOffset = deviceOffset(offset, window);
-
- bool canUseSrgb = false;
- // If there are any sRGB textures in the list, check if the destination
- // framebuffer is sRGB capable.
- for (int i = 0; i < textures->count(); ++i) {
- if (textures->flags(i).testFlag(QPlatformTextureList::TextureIsSrgb)) {
- GLint cap = 0;
- funcs->glGetIntegerv(GL_FRAMEBUFFER_SRGB_CAPABLE, &cap);
- if (cap)
- canUseSrgb = true;
- break;
- }
- }
-
- // Textures for renderToTexture widgets.
- for (int i = 0; i < textures->count(); ++i) {
- if (!textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop))
- blitTextureForWidget(textures, i, window, deviceWindowRect, blitter, offset, canUseSrgb);
- }
-
- // Backingstore texture with the normal widgets.
- GLuint textureId = 0;
- QOpenGLTextureBlitter::Origin origin = QOpenGLTextureBlitter::OriginTopLeft;
- if (QPlatformGraphicsBuffer *graphicsBuffer = backingStore->graphicsBuffer()) {
- if (graphicsBuffer->size() != textureSize) {
- if (this->textureId)
- funcs->glDeleteTextures(1, &this->textureId);
- funcs->glGenTextures(1, &this->textureId);
- funcs->glBindTexture(GL_TEXTURE_2D, this->textureId);
- QOpenGLContext *ctx = QOpenGLContext::currentContext();
- if (!ctx->isOpenGLES() || ctx->format().majorVersion() >= 3) {
- funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
- funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
- }
- funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-
- if (QPlatformGraphicsBufferHelper::lockAndBindToTexture(graphicsBuffer, &needsSwizzle, &premultiplied)) {
- textureSize = graphicsBuffer->size();
- } else {
- textureSize = QSize(0,0);
- }
-
- graphicsBuffer->unlock();
- } else if (!region.isEmpty()){
- funcs->glBindTexture(GL_TEXTURE_2D, this->textureId);
- QPlatformGraphicsBufferHelper::lockAndBindToTexture(graphicsBuffer, &needsSwizzle, &premultiplied);
- graphicsBuffer->unlock();
- }
-
- if (graphicsBuffer->origin() == QPlatformGraphicsBuffer::OriginBottomLeft)
- origin = QOpenGLTextureBlitter::OriginBottomLeft;
- textureId = this->textureId;
- } else {
- QPlatformBackingStore::TextureFlags flags;
- textureId = backingStore->toTexture(deviceRegion(region, window, offset), &textureSize, &flags);
- needsSwizzle = (flags & QPlatformBackingStore::TextureSwizzle) != 0;
- premultiplied = (flags & QPlatformBackingStore::TexturePremultiplied) != 0;
- if (flags & QPlatformBackingStore::TextureFlip)
- origin = QOpenGLTextureBlitter::OriginBottomLeft;
- }
-
- funcs->glEnable(GL_BLEND);
- if (premultiplied)
- funcs->glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);
- else
- funcs->glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);
-
- if (textureId) {
- if (needsSwizzle)
- blitter->setRedBlueSwizzle(true);
- // The backingstore is for the entire tlw.
- // In case of native children offset tells the position relative to the tlw.
- const QRect srcRect = toBottomLeftRect(deviceWindowRect.translated(deviceWindowOffset), textureSize.height());
- const QMatrix3x3 source = QOpenGLTextureBlitter::sourceTransform(srcRect,
- textureSize,
- origin);
- blitter->blit(textureId, QMatrix4x4(), source);
- if (needsSwizzle)
- blitter->setRedBlueSwizzle(false);
- }
-
- // Textures for renderToTexture widgets that have WA_AlwaysStackOnTop set.
- bool blendIsPremultiplied = premultiplied;
- for (int i = 0; i < textures->count(); ++i) {
- const QPlatformTextureList::Flags flags = textures->flags(i);
- if (flags.testFlag(QPlatformTextureList::NeedsPremultipliedAlphaBlending)) {
- if (!blendIsPremultiplied) {
- funcs->glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);
- blendIsPremultiplied = true;
- }
- } else {
- if (blendIsPremultiplied) {
- funcs->glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);
- blendIsPremultiplied = false;
- }
- }
- if (flags.testFlag(QPlatformTextureList::StacksOnTop))
- blitTextureForWidget(textures, i, window, deviceWindowRect, blitter, offset, canUseSrgb);
- }
-
- funcs->glDisable(GL_BLEND);
- blitter->release();
-
- context->swapBuffers(window);
-}
-
-GLuint QPlatformBackingStoreOpenGLSupport::toTexture(const QRegion &dirtyRegion, QSize *textureSize, QPlatformBackingStore::TextureFlags *flags) const
-{
- Q_ASSERT(textureSize);
- Q_ASSERT(flags);
-
- QImage image = backingStore->toImage();
- QSize imageSize = image.size();
-
- QOpenGLContext *ctx = QOpenGLContext::currentContext();
- GLenum internalFormat = GL_RGBA;
- GLuint pixelType = GL_UNSIGNED_BYTE;
-
- bool needsConversion = false;
- *flags = { };
- switch (image.format()) {
- case QImage::Format_ARGB32_Premultiplied:
- *flags |= QPlatformBackingStore::TexturePremultiplied;
- Q_FALLTHROUGH();
- case QImage::Format_RGB32:
- case QImage::Format_ARGB32:
- *flags |= QPlatformBackingStore::TextureSwizzle;
- break;
- case QImage::Format_RGBA8888_Premultiplied:
- *flags |= QPlatformBackingStore::TexturePremultiplied;
- Q_FALLTHROUGH();
- case QImage::Format_RGBX8888:
- case QImage::Format_RGBA8888:
- break;
- case QImage::Format_BGR30:
- case QImage::Format_A2BGR30_Premultiplied:
- if (!ctx->isOpenGLES() || ctx->format().majorVersion() >= 3) {
- pixelType = GL_UNSIGNED_INT_2_10_10_10_REV;
- internalFormat = GL_RGB10_A2;
- *flags |= QPlatformBackingStore::TexturePremultiplied;
- } else {
- needsConversion = true;
- }
- break;
- case QImage::Format_RGB30:
- case QImage::Format_A2RGB30_Premultiplied:
- if (!ctx->isOpenGLES() || ctx->format().majorVersion() >= 3) {
- pixelType = GL_UNSIGNED_INT_2_10_10_10_REV;
- internalFormat = GL_RGB10_A2;
- *flags |= QPlatformBackingStore::TextureSwizzle | QPlatformBackingStore::TexturePremultiplied;
- } else {
- needsConversion = true;
- }
- break;
- default:
- needsConversion = true;
- break;
- }
- if (imageSize.isEmpty()) {
- *textureSize = imageSize;
- return 0;
- }
-
- // Must rely on the input only, not d_ptr.
- // With the default composeAndFlush() textureSize is &d_ptr->textureSize.
- bool resized = *textureSize != imageSize;
- if (dirtyRegion.isEmpty() && !resized)
- return textureId;
-
- *textureSize = imageSize;
-
- if (needsConversion)
- image = image.convertToFormat(QImage::Format_RGBA8888);
-
- // The image provided by the backingstore may have a stride larger than width * 4, for
- // instance on platforms that manually implement client-side decorations.
- static const int bytesPerPixel = 4;
- const qsizetype strideInPixels = image.bytesPerLine() / bytesPerPixel;
- const bool hasUnpackRowLength = !ctx->isOpenGLES() || ctx->format().majorVersion() >= 3;
-
- QOpenGLFunctions *funcs = ctx->functions();
-
- if (hasUnpackRowLength) {
- funcs->glPixelStorei(GL_UNPACK_ROW_LENGTH, strideInPixels);
- } else if (strideInPixels != image.width()) {
- // No UNPACK_ROW_LENGTH on ES 2.0 and yet we would need it. This case is typically
- // hit with QtWayland which is rarely used in combination with a ES2.0-only GL
- // implementation. Therefore, accept the performance hit and do a copy.
- image = image.copy();
- }
-
- if (resized) {
- if (textureId)
- funcs->glDeleteTextures(1, &textureId);
- funcs->glGenTextures(1, &textureId);
- funcs->glBindTexture(GL_TEXTURE_2D, textureId);
- if (!ctx->isOpenGLES() || ctx->format().majorVersion() >= 3) {
- funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
- funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
- }
- funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- funcs->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-
- funcs->glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, imageSize.width(), imageSize.height(), 0, GL_RGBA, pixelType,
- const_cast<uchar*>(image.constBits()));
- } else {
- funcs->glBindTexture(GL_TEXTURE_2D, textureId);
- QRect imageRect = image.rect();
- QRect rect = dirtyRegion.boundingRect() & imageRect;
-
- if (hasUnpackRowLength) {
- funcs->glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x(), rect.y(), rect.width(), rect.height(), GL_RGBA, pixelType,
- image.constScanLine(rect.y()) + rect.x() * bytesPerPixel);
- } else {
- // if the rect is wide enough it's cheaper to just
- // extend it instead of doing an image copy
- if (rect.width() >= imageRect.width() / 2) {
- rect.setX(0);
- rect.setWidth(imageRect.width());
- }
-
- // if the sub-rect is full-width we can pass the image data directly to
- // OpenGL instead of copying, since there's no gap between scanlines
-
- if (rect.width() == imageRect.width()) {
- funcs->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, rect.y(), rect.width(), rect.height(), GL_RGBA, pixelType,
- image.constScanLine(rect.y()));
- } else {
- funcs->glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x(), rect.y(), rect.width(), rect.height(), GL_RGBA, pixelType,
- image.copy(rect).constBits());
- }
- }
- }
-
- if (hasUnpackRowLength)
- funcs->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
-
- return textureId;
-}
-
-void qt_registerDefaultPlatformBackingStoreOpenGLSupport()
-{
- if (!QPlatformBackingStoreOpenGLSupportBase::factoryFunction()) {
- QPlatformBackingStoreOpenGLSupportBase::setFactoryFunction([]() -> QPlatformBackingStoreOpenGLSupportBase* {
- return new QPlatformBackingStoreOpenGLSupport;
- });
- }
-}
-
-#endif // QT_NO_OPENGL
-
-QT_END_NAMESPACE
diff --git a/src/openglwidgets/qopenglwidget.cpp b/src/openglwidgets/qopenglwidget.cpp
index 7c565e10f3..99743a29a9 100644
--- a/src/openglwidgets/qopenglwidget.cpp
+++ b/src/openglwidgets/qopenglwidget.cpp
@@ -55,9 +55,12 @@
#include <QtGui/private/qopenglcontext_p.h>
#include <QtOpenGL/private/qopenglframebufferobject_p.h>
#include <QtOpenGL/private/qopenglpaintdevice_p.h>
-#include <QtOpenGL/qpa/qplatformbackingstoreopenglsupport.h>
#include <QtWidgets/private/qwidget_p.h>
+#include <QtWidgets/private/qwidgetrepaintmanager_p.h>
+
+#include <QtGui/private/qrhi_p.h>
+#include <QtGui/private/qrhigles2_p.h>
QT_BEGIN_NAMESPACE
@@ -324,31 +327,34 @@ QT_BEGIN_NAMESPACE
\snippet code/doc_gui_widgets_qopenglwidget.cpp 4
- This is naturally not the only possible solution. One alternative is to use
- the \l{QOpenGLContext::aboutToBeDestroyed()}{aboutToBeDestroyed()} signal of
- QOpenGLContext. By connecting a slot, using direct connection, to this signal,
- it is possible to perform cleanup whenever the underlying native context
- handle, or the entire QOpenGLContext instance, is going to be released. The
- following snippet is in principle equivalent to the previous one:
+ This works for most cases, but not fully ideal as a generic solution. When
+ the widget is reparented so that it ends up in an entirely different
+ top-level window, something more is needed: by connecting to the
+ \l{QOpenGLContext::aboutToBeDestroyed()}{aboutToBeDestroyed()} signal of
+ QOpenGLContext, cleanup can be performed whenever the OpenGL context is about
+ to be released.
+
+ \note For widgets that change their associated top-level window multiple
+ times during their lifetime, a combined cleanup approach, as demonstrated in
+ the code snippet below, is essential. Whenever the widget or a parent of it
+ gets reparented so that the top-level window becomes different, the widget's
+ associated context is destroyed and a new one is created. This is then
+ followed by a call to initializeGL() where all OpenGL resources must get
+ reinitialized. Due to this the only option to perform proper cleanup is to
+ connect to the context's aboutToBeDestroyed() signal. Note that the context
+ in question may not be the current one when the signal gets emitted.
+ Therefore it is good practice to call makeCurrent() in the connected slot.
+ Additionally, the same cleanup steps must be performed from the derived
+ class' destructor, since the slot or lambda connected to the signal may not
+ invoked when the widget is being destroyed.
\snippet code/doc_gui_widgets_qopenglwidget.cpp 5
- \note For widgets that change their associated top-level window multiple times
- during their lifetime, a combined approach is essential. Whenever the widget
- or a parent of it gets reparented so that the top-level window becomes
- different, the widget's associated context is destroyed and a new one is
- created. This is then followed by a call to initializeGL() where all OpenGL
- resources must get reinitialized. Due to this the only option to perform
- proper cleanup is to connect to the context's aboutToBeDestroyed()
- signal. Note that the context in question may not be the current one when the
- signal gets emitted. Therefore it is good practice to call makeCurrent() in
- the connected slot. Additionally, the same cleanup steps must be performed
- from the derived class' destructor, since the slot connected to the signal
- will not get invoked when the widget is being destroyed.
-
\note When Qt::AA_ShareOpenGLContexts is set, the widget's context never
changes, not even when reparenting because the widget's associated texture is
- guaranteed to be accessible also from the new top-level's context.
+ going to be accessible also from the new top-level's context. Therefore,
+ acting on the aboutToBeDestroyed() signal of the context is not mandatory
+ with this flag set.
Proper cleanup is especially important due to context sharing. Even though
each QOpenGLWidget's associated context is destroyed together with the
@@ -361,7 +367,7 @@ QT_BEGIN_NAMESPACE
explicit cleanup for all resources and resource wrappers used in the
QOpenGLWidget.
- \section1 Limitations
+ \section1 Limitations and Other Considerations
Putting other widgets underneath and making the QOpenGLWidget transparent will
not lead to the expected results: The widgets underneath will not be
@@ -401,6 +407,20 @@ QT_BEGIN_NAMESPACE
each frame. To restore the preserved behavior, call setUpdateBehavior() with
\c PartialUpdate.
+ \note When dynamically adding a QOpenGLWidget into a widget hierarchy, e.g.
+ by parenting a new QOpenGLWidget to a widget where the corresponding
+ top-level widget is already shown on screen, the associated native window may
+ get implicitly destroyed and recreated if the QOpenGLWidget is the first of
+ its kind within its window. This is because the window type changes from
+ \l{QSurface::RasterSurface}{RasterSurface} to
+ \l{QSurface::OpenGLSurface}{OpenGLSurface} and that has platform-specific
+ implications. This behavior is new in Qt 6.4.
+
+ Once a QOpenGLWidget is added to a widget hierarchy, the contents of the
+ top-level window is flushed via OpenGL-based rendering. Widgets other than
+ the QOpenGLWidget continue to draw their content using a software-based
+ painter, but the final composition is done through the 3D API.
+
\note Displaying a QOpenGLWidget requires an alpha channel in the associated
top-level window's backing store due to the way composition with other
QWidget-based content works. If there is no alpha channel, the content
@@ -518,13 +538,16 @@ public:
QOpenGLWidgetPrivate() = default;
void reset();
+ void resetRhiDependentResources();
void recreateFbo();
+ void ensureRhiDependentResources();
- GLuint textureId() const override;
+ QRhiTexture *texture() const override;
QPlatformTextureList::Flags textureListFlags() override;
+ QPlatformBackingStoreRhiConfig rhiConfig() const override { return { QPlatformBackingStoreRhiConfig::OpenGL }; }
+
void initialize();
- void invokeUserPaint();
void render();
void invalidateFbo();
@@ -539,6 +562,7 @@ public:
void resolveSamples() override;
QOpenGLContext *context = nullptr;
+ QRhiTexture *wrapperTexture = nullptr;
QOpenGLFramebufferObject *fbo = nullptr;
QOpenGLFramebufferObject *resolvedFbo = nullptr;
QOffscreenSurface *surface = nullptr;
@@ -605,9 +629,9 @@ void QOpenGLWidgetPaintDevice::ensureActiveTarget()
wd->flushPending = true;
}
-GLuint QOpenGLWidgetPrivate::textureId() const
+QRhiTexture *QOpenGLWidgetPrivate::texture() const
{
- return resolvedFbo ? resolvedFbo->texture() : (fbo ? fbo->texture() : 0);
+ return wrapperTexture;
}
#ifndef GL_SRGB
@@ -654,6 +678,8 @@ void QOpenGLWidgetPrivate::reset()
delete resolvedFbo;
resolvedFbo = nullptr;
+ resetRhiDependentResources();
+
if (initialized)
q->doneCurrent();
@@ -667,6 +693,16 @@ void QOpenGLWidgetPrivate::reset()
initialized = fakeHidden = inBackingStorePaint = false;
}
+void QOpenGLWidgetPrivate::resetRhiDependentResources()
+{
+ // QRhi resource created from the QRhi. These must be released whenever the
+ // widget gets associated with a different QRhi, even when all OpenGL
+ // contexts share resources.
+
+ delete wrapperTexture;
+ wrapperTexture = nullptr;
+}
+
void QOpenGLWidgetPrivate::recreateFbo()
{
Q_Q(QOpenGLWidget);
@@ -705,9 +741,34 @@ void QOpenGLWidgetPrivate::recreateFbo()
paintDevice->setSize(deviceSize);
paintDevice->setDevicePixelRatio(q->devicePixelRatio());
+ ensureRhiDependentResources();
+
emit q->resized();
}
+void QOpenGLWidgetPrivate::ensureRhiDependentResources()
+{
+ Q_Q(QOpenGLWidget);
+
+ QRhi *rhi = nullptr;
+ if (QWidgetRepaintManager *repaintManager = QWidgetPrivate::get(q->window())->maybeRepaintManager())
+ rhi = repaintManager->rhi();
+
+ // If there is no rhi, because we are completely offscreen, then there's no wrapperTexture either
+ if (rhi) {
+ const QSize deviceSize = q->size() * q->devicePixelRatio();
+ if (!wrapperTexture || wrapperTexture->pixelSize() != deviceSize) {
+ const uint textureId = resolvedFbo ? resolvedFbo->texture() : (fbo ? fbo->texture() : 0);
+ if (!wrapperTexture)
+ wrapperTexture = rhi->newTexture(QRhiTexture::RGBA8, deviceSize, 1, QRhiTexture::RenderTarget);
+ else
+ wrapperTexture->setPixelSize(deviceSize);
+ if (!wrapperTexture->createFrom({textureId, 0 }))
+ qWarning("QOpenGLWidget: Failed to create wrapper texture");
+ }
+ }
+}
+
void QOpenGLWidgetPrivate::beginCompose()
{
Q_Q(QOpenGLWidget);
@@ -735,11 +796,7 @@ void QOpenGLWidgetPrivate::initialize()
// If no global shared context get our toplevel's context with which we
// will share in order to make the texture usable by the underlying window's backingstore.
QWidget *tlw = q->window();
- QOpenGLContext *shareContext = qt_gl_global_share_context();
- if (!shareContext)
- shareContext = get(tlw)->shareContext();
- // If shareContext is null, showing content on-screen will not work.
- // However, offscreen rendering and grabFramebuffer() will stay fully functional.
+ QWidgetPrivate *tlwd = get(tlw);
// Do not include the sample count. Requesting a multisampled context is not necessary
// since we render into an FBO, never to an actual surface. What's more, attempting to
@@ -748,17 +805,44 @@ void QOpenGLWidgetPrivate::initialize()
requestedSamples = requestedFormat.samples();
requestedFormat.setSamples(0);
- auto ctx = std::make_unique<QOpenGLContext>();
- ctx->setFormat(requestedFormat);
- if (shareContext) {
- ctx->setShareContext(shareContext);
- ctx->setScreen(shareContext->screen());
+ QRhi *rhi = nullptr;
+ if (QWidgetRepaintManager *repaintManager = tlwd->maybeRepaintManager())
+ rhi = repaintManager->rhi();
+
+ // Could be that something else already initialized the window with some
+ // other graphics API for the QRhi, that's not good.
+ if (rhi && rhi->backend() != QRhi::OpenGLES2) {
+ qWarning("The top-level window is not using OpenGL for composition, '%s' is not compatible with QOpenGLWidget",
+ rhi->backendName());
+ return;
}
- if (Q_UNLIKELY(!ctx->create())) {
+
+ // If rhi or contextFromRhi is null, showing content on-screen will not work.
+ // However, offscreen rendering and grabFramebuffer() will stay fully functional.
+
+ QOpenGLContext *contextFromRhi = rhi ? static_cast<const QRhiGles2NativeHandles *>(rhi->nativeHandles())->context : nullptr;
+
+ context = new QOpenGLContext;
+ context->setFormat(requestedFormat);
+ if (contextFromRhi) {
+ context->setShareContext(contextFromRhi);
+ context->setScreen(contextFromRhi->screen());
+ }
+ if (Q_UNLIKELY(!context->create())) {
qWarning("QOpenGLWidget: Failed to create context");
return;
}
+ surface = new QOffscreenSurface;
+ surface->setFormat(context->format());
+ surface->setScreen(context->screen());
+ surface->create();
+
+ if (Q_UNLIKELY(!context->makeCurrent(surface))) {
+ qWarning("QOpenGLWidget: Failed to make context current");
+ return;
+ }
+
// Propagate settings that make sense only for the tlw. Note that this only
// makes sense for properties that get picked up even after the native
// window is created.
@@ -776,24 +860,10 @@ void QOpenGLWidgetPrivate::initialize()
}
}
- // The top-level window's surface is not good enough since it causes way too
- // much trouble with regards to the QSurfaceFormat for example. So just like
- // in QQuickWidget, use a dedicated QOffscreenSurface.
- surface = new QOffscreenSurface;
- surface->setFormat(ctx->format());
- surface->setScreen(ctx->screen());
- surface->create();
-
- if (Q_UNLIKELY(!ctx->makeCurrent(surface))) {
- qWarning("QOpenGLWidget: Failed to make context current");
- return;
- }
-
paintDevice = new QOpenGLWidgetPaintDevice(q);
paintDevice->setSize(q->size() * q->devicePixelRatio());
paintDevice->setDevicePixelRatio(q->devicePixelRatio());
- context = ctx.release();
initialized = true;
q->initializeGL();
@@ -810,13 +880,23 @@ void QOpenGLWidgetPrivate::resolveSamples()
}
}
-void QOpenGLWidgetPrivate::invokeUserPaint()
+void QOpenGLWidgetPrivate::render()
{
Q_Q(QOpenGLWidget);
+ if (fakeHidden || !initialized)
+ return;
+
+ q->makeCurrent();
+
QOpenGLContext *ctx = QOpenGLContext::currentContext();
Q_ASSERT(ctx && fbo);
+ if (updateBehavior == QOpenGLWidget::NoPartialUpdate && hasBeenComposed) {
+ invalidateFbo();
+ hasBeenComposed = false;
+ }
+
QOpenGLFunctions *f = ctx->functions();
QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = fbo->handle();
@@ -829,23 +909,6 @@ void QOpenGLWidgetPrivate::invokeUserPaint()
QOpenGLContextPrivate::get(ctx)->defaultFboRedirect = 0;
}
-void QOpenGLWidgetPrivate::render()
-{
- Q_Q(QOpenGLWidget);
-
- if (fakeHidden || !initialized)
- return;
-
- q->makeCurrent();
-
- if (updateBehavior == QOpenGLWidget::NoPartialUpdate && hasBeenComposed) {
- invalidateFbo();
- hasBeenComposed = false;
- }
-
- invokeUserPaint();
-}
-
void QOpenGLWidgetPrivate::invalidateFbo()
{
QOpenGLExtensions *f = static_cast<QOpenGLExtensions *>(QOpenGLContext::currentContext()->functions());
@@ -933,7 +996,8 @@ QOpenGLWidget::QOpenGLWidget(QWidget *parent, Qt::WindowFlags f)
: QWidget(*(new QOpenGLWidgetPrivate), parent, f)
{
Q_D(QOpenGLWidget);
- if (Q_UNLIKELY(!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::RasterGLSurface)))
+ if (Q_UNLIKELY(!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::RhiBasedRendering)
+ || !QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::OpenGL)))
qWarning("QOpenGLWidget is not supported on this platform.");
else
d->setRenderToTexture();
@@ -958,6 +1022,12 @@ QOpenGLWidget::QOpenGLWidget(QWidget *parent, Qt::WindowFlags f)
*/
QOpenGLWidget::~QOpenGLWidget()
{
+ // NB! resetting graphics resources must be done from this destructor,
+ // *not* from the private class' destructor. This is due to how destruction
+ // works and due to the QWidget dtor (for toplevels) destroying the repaint
+ // manager and rhi before the (QObject) private gets destroyed. Hence must
+ // do it here early on.
+
Q_D(QOpenGLWidget);
d->reset();
}
@@ -1249,11 +1319,13 @@ void QOpenGLWidget::paintEvent(QPaintEvent *e)
{
Q_UNUSED(e);
Q_D(QOpenGLWidget);
- if (!d->initialized)
- return;
- if (updatesEnabled())
- d->render();
+ d->initialize();
+ if (d->initialized) {
+ d->ensureRhiDependentResources();
+ if (updatesEnabled())
+ d->render();
+ }
}
/*!
@@ -1375,6 +1447,9 @@ bool QOpenGLWidget::event(QEvent *e)
{
Q_D(QOpenGLWidget);
switch (e->type()) {
+ case QEvent::WindowAboutToChangeInternal:
+ d->resetRhiDependentResources();
+ break;
case QEvent::WindowChangeInternal:
if (QCoreApplication::testAttribute(Qt::AA_ShareOpenGLContexts))
break;
@@ -1384,22 +1459,22 @@ bool QOpenGLWidget::event(QEvent *e)
break;
Q_FALLTHROUGH();
case QEvent::Show: // reparenting may not lead to a resize so reinitalize on Show too
- if (d->initialized && window()->windowHandle()
- && d->context->shareContext() != QWidgetPrivate::get(window())->shareContext())
- {
+ if (d->initialized && !d->wrapperTexture && window()->windowHandle()) {
// Special case: did grabFramebuffer() for a hidden widget that then became visible.
// Recreate all resources since the context now needs to share with the TLW's.
if (!QCoreApplication::testAttribute(Qt::AA_ShareOpenGLContexts))
d->reset();
}
- if (!d->initialized && !size().isEmpty() && window()->windowHandle()) {
- d->initialize();
- if (d->initialized) {
- d->recreateFbo();
- // QTBUG-89812: generate a paint event, like resize would do,
- // otherwise a QOpenGLWidget in a QDockWidget may not show the
- // content upon (un)docking.
- d->sendPaintEvent(QRect(QPoint(0, 0), size()));
+ if (QWidgetRepaintManager *repaintManager = QWidgetPrivate::get(window())->maybeRepaintManager()) {
+ if (!d->initialized && !size().isEmpty() && repaintManager->rhi()) {
+ d->initialize();
+ if (d->initialized) {
+ d->recreateFbo();
+ // QTBUG-89812: generate a paint event, like resize would do,
+ // otherwise a QOpenGLWidget in a QDockWidget may not show the
+ // content upon (un)docking.
+ d->sendPaintEvent(QRect(QPoint(0, 0), size()));
+ }
}
}
break;
@@ -1413,8 +1488,6 @@ bool QOpenGLWidget::event(QEvent *e)
return QWidget::event(e);
}
-Q_CONSTRUCTOR_FUNCTION(qt_registerDefaultPlatformBackingStoreOpenGLSupport);
-
QT_END_NAMESPACE
#include "moc_qopenglwidget.cpp"
diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.h b/src/plugins/platforms/cocoa/qcocoabackingstore.h
index a91407da5d..262920de47 100644
--- a/src/plugins/platforms/cocoa/qcocoabackingstore.h
+++ b/src/plugins/platforms/cocoa/qcocoabackingstore.h
@@ -74,10 +74,12 @@ public:
bool scroll(const QRegion &region, int dx, int dy) override;
void flush(QWindow *, const QRegion &, const QPoint &) override;
-#ifndef QT_NO_OPENGL
- void composeAndFlush(QWindow *window, const QRegion &region, const QPoint &offset,
- QPlatformTextureList *textures, bool translucentBackground) override;
-#endif
+
+ FlushResult rhiFlush(QWindow *window,
+ const QRegion &region,
+ const QPoint &offset,
+ QPlatformTextureList *textures,
+ bool translucentBackground) override;
QImage toImage() const override;
QPlatformGraphicsBuffer *graphicsBuffer() const override;
diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.mm b/src/plugins/platforms/cocoa/qcocoabackingstore.mm
index a09176cfaa..8f3e3036ad 100644
--- a/src/plugins/platforms/cocoa/qcocoabackingstore.mm
+++ b/src/plugins/platforms/cocoa/qcocoabackingstore.mm
@@ -428,20 +428,21 @@ void QCALayerBackingStore::windowDestroyed(QObject *object)
m_subWindowBackingstores.erase(window);
}
-#ifndef QT_NO_OPENGL
-void QCALayerBackingStore::composeAndFlush(QWindow *window, const QRegion &region, const QPoint &offset,
- QPlatformTextureList *textures, bool translucentBackground)
+QPlatformBackingStore::FlushResult QCALayerBackingStore::rhiFlush(QWindow *window,
+ const QRegion &region,
+ const QPoint &offset,
+ QPlatformTextureList *textures,
+ bool translucentBackground)
{
if (!m_buffers.back()) {
qCWarning(lcQpaBackingStore) << "Tried to flush backingstore without painting to it first";
- return;
+ return FlushFailed;
}
finalizeBackBuffer();
- QPlatformBackingStore::composeAndFlush(window, region, offset, textures, translucentBackground);
+ return QPlatformBackingStore::rhiFlush(window, region, offset, textures, translucentBackground);
}
-#endif
QImage QCALayerBackingStore::toImage() const
{
diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm
index 92edeac558..3b2cf17f40 100644
--- a/src/plugins/platforms/cocoa/qcocoaintegration.mm
+++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm
@@ -68,7 +68,7 @@
#include <QtGui/private/qcoregraphics_p.h>
#include <QtGui/private/qopenglcontext_p.h>
-
+#include <QtGui/private/qrhibackingstore_p.h>
#include <QtGui/private/qfontengine_coretext_p.h>
#include <IOKit/graphics/IOGraphicsLib.h>
@@ -339,7 +339,15 @@ QPlatformBackingStore *QCocoaIntegration::createPlatformBackingStore(QWindow *wi
return nullptr;
}
- return new QCALayerBackingStore(window);
+ switch (window->surfaceType()) {
+ case QSurface::RasterSurface:
+ return new QCALayerBackingStore(window);
+ case QSurface::MetalSurface:
+ case QSurface::OpenGLSurface:
+ return new QRhiBackingStore(window);
+ default:
+ return nullptr;
+ }
}
QAbstractEventDispatcher *QCocoaIntegration::createEventDispatcher() const
diff --git a/src/plugins/platforms/ios/CMakeLists.txt b/src/plugins/platforms/ios/CMakeLists.txt
index 204add279c..a55b30440a 100644
--- a/src/plugins/platforms/ios/CMakeLists.txt
+++ b/src/plugins/platforms/ios/CMakeLists.txt
@@ -12,7 +12,6 @@ qt_internal_add_plugin(QIOSIntegrationPlugin
plugin.mm
qiosapplicationdelegate.h qiosapplicationdelegate.mm
qiosapplicationstate.h qiosapplicationstate.mm
- qiosbackingstore.h qiosbackingstore.mm
qioscontext.h qioscontext.mm
qioseventdispatcher.h qioseventdispatcher.mm
qiosglobal.h qiosglobal.mm
diff --git a/src/plugins/platforms/ios/qiosbackingstore.mm b/src/plugins/platforms/ios/qiosbackingstore.mm
deleted file mode 100644
index 074a8ee56d..0000000000
--- a/src/plugins/platforms/ios/qiosbackingstore.mm
+++ /dev/null
@@ -1,95 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the plugins of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** 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 Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** 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-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include "qiosbackingstore.h"
-#include "qioswindow.h"
-
-#include <QtGui/QOpenGLContext>
-#include <QtGui/private/qwindow_p.h>
-
-#include <QtOpenGL/qpa/qplatformbackingstoreopenglsupport.h>
-
-#include <QtDebug>
-
-QT_BEGIN_NAMESPACE
-
-/*!
- \class QIOSBackingStore
-
- QBackingStore enables the use of QPainter to paint on a QWindow, as opposed
- to rendering to a QWindow through the use of OpenGL with QOpenGLContext.
-*/
-QIOSBackingStore::QIOSBackingStore(QWindow *window)
- : QRasterBackingStore(window)
-{
- // We use the surface both for raster operations and for GL drawing (when
- // we blit the raster image), so the type needs to cover both use cases.
- if (window->surfaceType() == QSurface::RasterSurface)
- window->setSurfaceType(QSurface::RasterGLSurface);
-
- Q_ASSERT_X(window->surfaceType() != QSurface::OpenGLSurface, "QIOSBackingStore",
- "QBackingStore on iOS can only be used with raster-enabled surfaces.");
-}
-
-QIOSBackingStore::~QIOSBackingStore()
-{
-}
-
-void QIOSBackingStore::flush(QWindow *window, const QRegion &region, const QPoint &offset)
-{
- Q_ASSERT(!qt_window_private(window)->compositing);
-
- Q_UNUSED(region);
- Q_UNUSED(offset);
-
- if (window != this->window()) {
- // We skip flushing raster-based child windows, to avoid the extra cost of copying from the
- // parent FBO into the child FBO. Since the child is already drawn inside the parent FBO, it
- // will become visible when flushing the parent. The only case we end up not supporting is if
- // the child window overlaps a sibling window that's draws using a separate QOpenGLContext.
- return;
- }
-
- static QPlatformTextureList emptyTextureList;
- composeAndFlush(window, region, offset, &emptyTextureList, false);
-}
-
-Q_CONSTRUCTOR_FUNCTION(qt_registerDefaultPlatformBackingStoreOpenGLSupport);
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/ios/qiosintegration.mm b/src/plugins/platforms/ios/qiosintegration.mm
index 8938f97ae5..54ef5b2567 100644
--- a/src/plugins/platforms/ios/qiosintegration.mm
+++ b/src/plugins/platforms/ios/qiosintegration.mm
@@ -41,7 +41,6 @@
#include "qioseventdispatcher.h"
#include "qiosglobal.h"
#include "qioswindow.h"
-#include "qiosbackingstore.h"
#include "qiosscreen.h"
#include "qiosplatformaccessibility.h"
#include "qioscontext.h"
@@ -55,6 +54,7 @@
#include <QtGui/qpointingdevice.h>
#include <QtGui/private/qguiapplication_p.h>
+#include <QtGui/private/qrhibackingstore_p.h>
#include <qoffscreensurface.h>
#include <qpa/qplatformoffscreensurface.h>
@@ -185,10 +185,9 @@ QPlatformWindow *QIOSIntegration::createPlatformWindow(QWindow *window) const
return new QIOSWindow(window);
}
-// Used when the QWindow's surface type is set by the client to QSurface::RasterSurface
QPlatformBackingStore *QIOSIntegration::createPlatformBackingStore(QWindow *window) const
{
- return new QIOSBackingStore(window);
+ return new QRhiBackingStore(window);
}
// Used when the QWindow's surface type is set by the client to QSurface::OpenGLSurface
diff --git a/src/plugins/platforms/ios/qioswindow.mm b/src/plugins/platforms/ios/qioswindow.mm
index 864eef3641..8e0bb1aab7 100644
--- a/src/plugins/platforms/ios/qioswindow.mm
+++ b/src/plugins/platforms/ios/qioswindow.mm
@@ -65,6 +65,9 @@ QIOSWindow::QIOSWindow(QWindow *window)
, m_windowLevel(0)
{
#ifdef Q_OS_IOS
+ if (window->surfaceType() == QSurface::RasterSurface)
+ window->setSurfaceType(QSurface::MetalSurface);
+
if (window->surfaceType() == QSurface::MetalSurface)
m_view = [[QUIMetalView alloc] initWithQIOSWindow:this];
else
diff --git a/src/plugins/platforms/wasm/qwasmbackingstore.cpp b/src/plugins/platforms/wasm/qwasmbackingstore.cpp
index 989c2a1b90..90fb168288 100644
--- a/src/plugins/platforms/wasm/qwasmbackingstore.cpp
+++ b/src/plugins/platforms/wasm/qwasmbackingstore.cpp
@@ -173,7 +173,7 @@ void QWasmBackingStore::resize(const QSize &size, const QRegion &staticContents)
QImage QWasmBackingStore::toImage() const
{
- // used by QPlatformBackingStore::composeAndFlush
+ // used by QPlatformBackingStore::rhiFlush
return m_image;
}
diff --git a/src/plugins/platforms/wasm/qwasmintegration.cpp b/src/plugins/platforms/wasm/qwasmintegration.cpp
index 765f89052e..5a09cc710a 100644
--- a/src/plugins/platforms/wasm/qwasmintegration.cpp
+++ b/src/plugins/platforms/wasm/qwasmintegration.cpp
@@ -41,7 +41,6 @@
#include "qwasmwindow.h"
#ifndef QT_NO_OPENGL
# include "qwasmbackingstore.h"
-# include <QtOpenGL/qpa/qplatformbackingstoreopenglsupport.h>
#endif
#include "qwasmfontdatabase.h"
#if defined(Q_OS_UNIX)
diff --git a/src/plugins/platforms/windows/qwindowsgdiintegration.cpp b/src/plugins/platforms/windows/qwindowsgdiintegration.cpp
index bb24060dbe..c88f669eb5 100644
--- a/src/plugins/platforms/windows/qwindowsgdiintegration.cpp
+++ b/src/plugins/platforms/windows/qwindowsgdiintegration.cpp
@@ -45,10 +45,6 @@
#include <QtCore/qdebug.h>
#include <QtGui/private/qpixmap_raster_p.h>
-#if QT_CONFIG(opengl)
-#include <QtOpenGL/qpa/qplatformbackingstoreopenglsupport.h>
-#endif
-
QT_BEGIN_NAMESPACE
class QWindowsGdiIntegrationPrivate
diff --git a/src/plugins/platforms/xcb/qxcbbackingstore.cpp b/src/plugins/platforms/xcb/qxcbbackingstore.cpp
index 94edd87eb7..1ea64f9f92 100644
--- a/src/plugins/platforms/xcb/qxcbbackingstore.cpp
+++ b/src/plugins/platforms/xcb/qxcbbackingstore.cpp
@@ -901,17 +901,18 @@ void QXcbBackingStore::render(xcb_window_t window, const QRegion &region, const
m_image->put(window, region, offset);
}
-#ifndef QT_NO_OPENGL
-void QXcbBackingStore::composeAndFlush(QWindow *window, const QRegion &region, const QPoint &offset,
- QPlatformTextureList *textures,
- bool translucentBackground)
+QPlatformBackingStore::FlushResult QXcbBackingStore::rhiFlush(QWindow *window,
+ const QRegion &region,
+ const QPoint &offset,
+ QPlatformTextureList *textures,
+ bool translucentBackground)
{
if (!m_image || m_image->size().isEmpty())
- return;
+ return FlushFailed;
m_image->flushScrolledRegion(true);
- QPlatformBackingStore::composeAndFlush(window, region, offset, textures, translucentBackground);
+ QPlatformBackingStore::rhiFlush(window, region, offset, textures, translucentBackground);
QXcbWindow *platformWindow = static_cast<QXcbWindow *>(window->handle());
if (platformWindow->needsSync()) {
@@ -919,8 +920,9 @@ void QXcbBackingStore::composeAndFlush(QWindow *window, const QRegion &region, c
} else {
xcb_flush(xcb_connection());
}
+
+ return FlushSuccess;
}
-#endif // QT_NO_OPENGL
void QXcbBackingStore::resize(const QSize &size, const QRegion &)
{
diff --git a/src/plugins/platforms/xcb/qxcbbackingstore.h b/src/plugins/platforms/xcb/qxcbbackingstore.h
index 0c30929d4e..35ff7a0f4c 100644
--- a/src/plugins/platforms/xcb/qxcbbackingstore.h
+++ b/src/plugins/platforms/xcb/qxcbbackingstore.h
@@ -59,11 +59,11 @@ public:
QPaintDevice *paintDevice() override;
void flush(QWindow *window, const QRegion &region, const QPoint &offset) override;
-#ifndef QT_NO_OPENGL
- void composeAndFlush(QWindow *window, const QRegion &region, const QPoint &offset,
+ FlushResult rhiFlush(QWindow *window,
+ const QRegion &region,
+ const QPoint &offset,
QPlatformTextureList *textures,
bool translucentBackground) override;
-#endif
QImage toImage() const override;
QPlatformGraphicsBuffer *graphicsBuffer() const override;
diff --git a/src/plugins/platforms/xcb/qxcbintegration.cpp b/src/plugins/platforms/xcb/qxcbintegration.cpp
index 2dd2bcf11f..9fd9819b2b 100644
--- a/src/plugins/platforms/xcb/qxcbintegration.cpp
+++ b/src/plugins/platforms/xcb/qxcbintegration.cpp
@@ -61,9 +61,6 @@
#include <QtGui/private/qgenericunixfontdatabase_p.h>
#include <QtGui/private/qgenericunixservices_p.h>
-#if QT_CONFIG(opengl)
-#include <QtOpenGL/qpa/qplatformbackingstoreopenglsupport.h>
-#endif
#include <stdio.h>
diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp
index 0a0957bf5e..1527ec679f 100644
--- a/src/widgets/kernel/qwidget.cpp
+++ b/src/widgets/kernel/qwidget.cpp
@@ -77,9 +77,6 @@
#include "qscopeguard.h"
#include <QtGui/private/qhighdpiscaling_p.h>
#include <QtGui/qinputmethod.h>
-#include <QtGui/qopenglcontext.h>
-#include <QtGui/private/qopenglcontext_p.h>
-#include <QtGui/qoffscreensurface.h>
#if QT_CONFIG(graphicseffect)
#include <private/qgraphicseffect_p.h>
@@ -178,10 +175,8 @@ QWidgetPrivate::QWidgetPrivate(int version)
#ifndef QT_NO_IM
, inheritsInputMethodHints(0)
#endif
-#ifndef QT_NO_OPENGL
, renderToTextureReallyDirty(1)
- , renderToTextureComposeActive(0)
-#endif
+ , usesRhiFlush(0)
, childrenHiddenByWState(0)
, childrenShownByExpose(0)
#if defined(Q_OS_WIN)
@@ -1114,6 +1109,47 @@ QScreen *QWidgetPrivate::associatedScreen() const
return nullptr;
}
+// finds the first rhiconfig in the hierarchy that has enable==true
+static bool q_evaluateRhiConfigRecursive(const QWidget *w, QPlatformBackingStoreRhiConfig *outConfig, QSurface::SurfaceType *outType)
+{
+ QPlatformBackingStoreRhiConfig config = QWidgetPrivate::get(w)->rhiConfig();
+ if (config.isEnabled()) {
+ if (outConfig)
+ *outConfig = config;
+ if (outType)
+ *outType = QBackingStoreRhiSupport::surfaceTypeForConfig(config);
+ return true;
+ }
+ QObjectList children = w->children();
+ for (int i = 0; i < children.size(); i++) {
+ if (children.at(i)->isWidgetType()) {
+ const QWidget *childWidget = qobject_cast<const QWidget *>(children.at(i));
+ if (childWidget) {
+ if (q_evaluateRhiConfigRecursive(childWidget, outConfig, outType))
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+// First tries q_evaluateRhiConfigRecursive, then if that did not indicate that rhi is wanted,
+// then checks env.vars or something else to see if we need to force using rhi-based composition.
+bool q_evaluateRhiConfig(const QWidget *w, QPlatformBackingStoreRhiConfig *outConfig, QSurface::SurfaceType *outType)
+{
+ if (q_evaluateRhiConfigRecursive(w, outConfig, outType)) {
+ qCDebug(lcWidgetPainting) << "Tree with root" << w << "evaluates to flushing with QRhi";
+ return true;
+ }
+
+ if (QBackingStoreRhiSupport::checkForceRhi(outConfig, outType)) {
+ qCDebug(lcWidgetPainting) << "Tree with root" << w << "evaluated to forced flushing with QRhi";
+ return true;
+ }
+
+ return false;
+}
+
// ### fixme: Qt 6: Remove parameter window from QWidget::create()
/*!
@@ -1336,14 +1372,21 @@ void QWidgetPrivate::create()
#endif
QBackingStore *store = q->backingStore();
+ usesRhiFlush = false;
if (!store) {
if (q->windowType() != Qt::Desktop) {
- if (q->isWindow())
+ if (q->isWindow()) {
q->setBackingStore(new QBackingStore(win));
+ QPlatformBackingStoreRhiConfig rhiConfig;
+ usesRhiFlush = q_evaluateRhiConfig(q, &rhiConfig, nullptr);
+ topData()->backingStore->handle()->setRhiConfig(rhiConfig);
+ }
} else {
q->setAttribute(Qt::WA_PaintOnScreen, true);
}
+ } else if (win->handle()) {
+ usesRhiFlush = q_evaluateRhiConfig(q, nullptr, nullptr);
}
setWindowModified_helper();
@@ -1685,10 +1728,7 @@ void QWidgetPrivate::deleteTLSysExtra()
extra->topextra->repaintManager.reset(nullptr);
deleteBackingStore(this);
-#ifndef QT_NO_OPENGL
extra->topextra->widgetTextures.clear();
- extra->topextra->shareContext.reset();
-#endif
//the toplevel might have a context with a "qglcontext associated with it. We need to
//delete the qglcontext before we delete the qplatformopenglcontext.
@@ -5538,30 +5578,22 @@ void QWidgetPrivate::drawWidget(QPaintDevice *pdev, const QRegion &rgn, const QP
//paint the background
if ((asRoot || q->autoFillBackground() || onScreen || q->testAttribute(Qt::WA_StyledBackground))
&& !q->testAttribute(Qt::WA_OpaquePaintEvent) && !q->testAttribute(Qt::WA_NoSystemBackground)) {
-#ifndef QT_NO_OPENGL
beginBackingStorePainting();
-#endif
QPainter p(q);
paintBackground(&p, toBePainted, (asRoot || onScreen) ? (flags | DrawAsRoot) : DrawWidgetFlags());
-#ifndef QT_NO_OPENGL
endBackingStorePainting();
-#endif
}
if (!sharedPainter)
setSystemClip(pdev->paintEngine(), pdev->devicePixelRatio(), toBePainted.translated(offset));
if (!onScreen && !asRoot && !isOpaque && q->testAttribute(Qt::WA_TintedBackground)) {
-#ifndef QT_NO_OPENGL
beginBackingStorePainting();
-#endif
QPainter p(q);
QColor tint = q->palette().window().color();
tint.setAlphaF(.6f);
p.fillRect(toBePainted.boundingRect(), tint);
-#ifndef QT_NO_OPENGL
endBackingStorePainting();
-#endif
}
}
@@ -5572,7 +5604,6 @@ void QWidgetPrivate::drawWidget(QPaintDevice *pdev, const QRegion &rgn, const QP
#endif
bool skipPaintEvent = false;
-#ifndef QT_NO_OPENGL
if (renderToTexture) {
// This widget renders into a texture which is composed later. We just need to
// punch a hole in the backingstore, so the texture will be visible.
@@ -5598,7 +5629,6 @@ void QWidgetPrivate::drawWidget(QPaintDevice *pdev, const QRegion &rgn, const QP
else
skipPaintEvent = true;
}
-#endif // QT_NO_OPENGL
if (!skipPaintEvent) {
//actually send the paint event
@@ -5654,10 +5684,8 @@ void QWidgetPrivate::sendPaintEvent(const QRegion &toBePainted)
QPaintEvent e(toBePainted);
QCoreApplication::sendSpontaneousEvent(q, &e);
-#ifndef QT_NO_OPENGL
if (renderToTexture)
resolveSamples();
-#endif // QT_NO_OPENGL
}
void QWidgetPrivate::render(QPaintDevice *target, const QPoint &targetOffset,
@@ -9232,9 +9260,7 @@ bool QWidget::event(QEvent *event)
}
if (d->data.fnt.d->dpi != logicalDpiY())
d->updateFont(d->data.fnt);
-#ifndef QT_NO_OPENGL
d->renderToTextureReallyDirty = 1;
-#endif
break;
case QEvent::DynamicPropertyChange: {
const QByteArray &propName = static_cast<QDynamicPropertyChangeEvent *>(event)->propertyName();
@@ -10511,22 +10537,20 @@ void QWidget::setParent(QWidget *parent)
setParent((QWidget*)parent, windowFlags() & ~Qt::WindowType_Mask);
}
-#ifndef QT_NO_OPENGL
-static void sendWindowChangeToTextureChildrenRecursively(QWidget *widget)
+static void sendWindowChangeToTextureChildrenRecursively(QWidget *widget, QEvent::Type eventType)
{
QWidgetPrivate *d = QWidgetPrivate::get(widget);
if (d->renderToTexture) {
- QEvent e(QEvent::WindowChangeInternal);
+ QEvent e(eventType);
QCoreApplication::sendEvent(widget, &e);
}
for (int i = 0; i < d->children.size(); ++i) {
QWidget *w = qobject_cast<QWidget *>(d->children.at(i));
if (w && !w->isWindow() && QWidgetPrivate::get(w)->textureChildSeen)
- sendWindowChangeToTextureChildrenRecursively(w);
+ sendWindowChangeToTextureChildrenRecursively(w, eventType);
}
}
-#endif
/*!
\overload
@@ -10582,6 +10606,12 @@ void QWidget::setParent(QWidget *parent, Qt::WindowFlags f)
QCoreApplication::sendEvent(this, &e);
}
}
+
+ // texture-based widgets need a pre-notification when their associated top-level window changes
+ // This is not under the wasCreated/newParent conditions above in order to also play nice with QDockWidget.
+ if (d->textureChildSeen && ((!parent && parentWidget()) || (parent && parent->window() != oldtlw)))
+ sendWindowChangeToTextureChildrenRecursively(this, QEvent::WindowAboutToChangeInternal);
+
// If we get parented into another window, children will be folded
// into the new parent's focus chain, so clear focus now.
if (newParent && isAncestorOf(focusWidget()) && !(f & Qt::Window))
@@ -10592,12 +10622,10 @@ void QWidget::setParent(QWidget *parent, Qt::WindowFlags f)
if (desktopWidget)
parent = nullptr;
-#ifndef QT_NO_OPENGL
if (d->textureChildSeen && parent) {
// set the textureChildSeen flag up the whole parent chain
QWidgetPrivate::get(parent)->setTextureChildSeen();
}
-#endif
if (QWidgetRepaintManager *oldPaintManager = oldtlw->d_func()->maybeRepaintManager()) {
if (newParent)
@@ -10659,12 +10687,11 @@ void QWidget::setParent(QWidget *parent, Qt::WindowFlags f)
QEvent e(QEvent::ParentChange);
QCoreApplication::sendEvent(this, &e);
}
-#ifndef QT_NO_OPENGL
- //renderToTexture widgets also need to know when their top-level window changes
- if (d->textureChildSeen && oldtlw != window()) {
- sendWindowChangeToTextureChildrenRecursively(this);
- }
-#endif
+
+ // texture-based widgets need another event when their top-level window
+ // changes (more precisely, has already changed at this point)
+ if (d->textureChildSeen && oldtlw != window())
+ sendWindowChangeToTextureChildrenRecursively(this, QEvent::WindowChangeInternal);
if (!wasCreated) {
if (isWindow() || parentWidget()->isVisible())
@@ -10690,6 +10717,20 @@ void QWidget::setParent(QWidget *parent, Qt::WindowFlags f)
if (d->extra && d->extra->hasWindowContainer)
QWindowContainer::parentWasChanged(this);
+
+ QWidget *newtlw = window();
+ if (oldtlw != newtlw) {
+ QSurface::SurfaceType surfaceType = QSurface::RasterSurface;
+ if (q_evaluateRhiConfig(this, nullptr, &surfaceType)) {
+ newtlw->d_func()->usesRhiFlush = true;
+ if (QWindow *w = newtlw->windowHandle()) {
+ if (w->surfaceType() != surfaceType) {
+ newtlw->destroy();
+ newtlw->create();
+ }
+ }
+ }
+ }
}
void QWidgetPrivate::setParent_sys(QWidget *newparent, Qt::WindowFlags f)
@@ -10947,7 +10988,7 @@ void QWidgetPrivate::repaint(T r)
return;
QTLWExtra *tlwExtra = q->window()->d_func()->maybeTopData();
- if (tlwExtra && tlwExtra->backingStore)
+ if (tlwExtra && tlwExtra->backingStore && tlwExtra->repaintManager)
tlwExtra->repaintManager->markDirty(r, q, QWidgetRepaintManager::UpdateNow);
}
@@ -11022,7 +11063,7 @@ void QWidgetPrivate::update(T r)
}
QTLWExtra *tlwExtra = q->window()->d_func()->maybeTopData();
- if (tlwExtra && tlwExtra->backingStore)
+ if (tlwExtra && tlwExtra->backingStore && tlwExtra->repaintManager)
tlwExtra->repaintManager->markDirty(clipped, q);
}
@@ -12158,27 +12199,6 @@ void QWidgetPrivate::adjustQuitOnCloseAttribute()
}
}
-QOpenGLContext *QWidgetPrivate::shareContext() const
-{
-#ifdef QT_NO_OPENGL
- return nullptr;
-#else
- if (!extra || !extra->topextra || !extra->topextra->window)
- return nullptr;
-
- if (!extra->topextra->shareContext) {
- auto ctx = std::make_unique<QOpenGLContext>();
- ctx->setShareContext(qt_gl_global_share_context());
- ctx->setFormat(extra->topextra->window->format());
- ctx->setScreen(extra->topextra->window->screen());
- ctx->create();
- extra->topextra->shareContext = std::move(ctx);
- }
- return extra->topextra->shareContext.get();
-#endif // QT_NO_OPENGL
-}
-
-#ifndef QT_NO_OPENGL
void QWidgetPrivate::sendComposeStatus(QWidget *w, bool end)
{
QWidgetPrivate *wd = QWidgetPrivate::get(w);
@@ -12194,7 +12214,6 @@ void QWidgetPrivate::sendComposeStatus(QWidget *w, bool end)
sendComposeStatus(w, end);
}
}
-#endif // QT_NO_OPENGL
Q_WIDGETS_EXPORT QWidgetData *qt_qwidget_data(QWidget *widget)
{
diff --git a/src/widgets/kernel/qwidget_p.h b/src/widgets/kernel/qwidget_p.h
index 41f343dfec..b8a30fde25 100644
--- a/src/widgets/kernel/qwidget_p.h
+++ b/src/widgets/kernel/qwidget_p.h
@@ -61,7 +61,6 @@
#include "QtCore/qset.h"
#include "QtGui/qregion.h"
#include "QtGui/qinputmethod.h"
-#include "QtGui/qopengl.h"
#include "QtGui/qsurfaceformat.h"
#include "QtGui/qscreen.h"
#include "QtWidgets/qsizepolicy.h"
@@ -77,6 +76,7 @@
#endif
#include <private/qgesture_p.h>
#include <qpa/qplatformbackingstore.h>
+#include <QtGui/private/qbackingstorerhisupport_p.h>
#include <vector>
#include <memory>
@@ -94,7 +94,6 @@ class QPixmap;
class QWidgetRepaintManager;
class QGraphicsProxyWidget;
class QWidgetItemV2;
-class QOpenGLContext;
class QStyle;
@@ -130,9 +129,6 @@ struct QTLWExtra {
QBackingStore *backingStore;
QPainter *sharedPainter;
QWidgetWindow *window;
-#ifndef QT_NO_OPENGL
- mutable std::unique_ptr<QOpenGLContext> shareContext;
-#endif
// Implicit pointers (shared_null).
QString caption; // widget caption
@@ -149,9 +145,7 @@ struct QTLWExtra {
Qt::WindowFlags savedFlags; // Save widget flags while showing fullscreen
QScreen *initialScreen; // Screen when passing a QDesktop[Screen]Widget as parent.
-#ifndef QT_NO_OPENGL
std::vector<std::unique_ptr<QPlatformTextureList>> widgetTextures;
-#endif
// *************************** Cross-platform bit fields ****************************
uint opacity : 8;
@@ -627,12 +621,11 @@ public:
inline QRect mapFromWS(const QRect &r) const
{ return r.translated(data.wrect.topLeft()); }
- QOpenGLContext *shareContext() const;
-
virtual QObject *focusObject() { return nullptr; }
-#ifndef QT_NO_OPENGL
- virtual GLuint textureId() const { return 0; }
+ virtual QPlatformBackingStoreRhiConfig rhiConfig() const { return {}; }
+
+ virtual QRhiTexture *texture() const { return nullptr; }
virtual QPlatformTextureList::Flags textureListFlags() {
Q_Q(QWidget);
return q->testAttribute(Qt::WA_AlwaysStackOnTop)
@@ -667,7 +660,6 @@ public:
virtual void resizeViewportFramebuffer() { }
// Called after each paint event.
virtual void resolveSamples() { }
-#endif
static void setWidgetParentHelper(QObject *widgetAsObject, QObject *newParent);
@@ -758,10 +750,8 @@ public:
#ifndef QT_NO_IM
uint inheritsInputMethodHints : 1;
#endif
-#ifndef QT_NO_OPENGL
uint renderToTextureReallyDirty : 1;
- uint renderToTextureComposeActive : 1;
-#endif
+ uint usesRhiFlush : 1;
uint childrenHiddenByWState : 1;
uint childrenShownByExpose : 1;
diff --git a/src/widgets/kernel/qwidgetrepaintmanager.cpp b/src/widgets/kernel/qwidgetrepaintmanager.cpp
index 16ff8ea721..1094c2c3b0 100644
--- a/src/widgets/kernel/qwidgetrepaintmanager.cpp
+++ b/src/widgets/kernel/qwidgetrepaintmanager.cpp
@@ -65,12 +65,11 @@
QT_BEGIN_NAMESPACE
-#ifndef QT_NO_OPENGL
Q_GLOBAL_STATIC(QPlatformTextureList, qt_dummy_platformTextureList)
// Watches one or more QPlatformTextureLists for changes in the lock state and
// triggers a backingstore sync when all the registered lists turn into
-// unlocked state. This is essential when a custom composeAndFlush()
+// unlocked state. This is essential when a custom rhiFlush()
// implementation in a platform plugin is not synchronous and keeps
// holding on to the textures for some time even after returning from there.
class QPlatformTextureListWatcher : public QObject
@@ -105,7 +104,6 @@ private:
QHash<QPlatformTextureList *, bool> m_locked;
QWidgetRepaintManager *m_repaintManager;
};
-#endif
// ---------------------------------------------------------------------------
@@ -374,7 +372,6 @@ void QWidgetRepaintManager::sendUpdateRequest(QWidget *widget, UpdateTime update
qCInfo(lcWidgetPainting) << "Sending update request to" << widget << "with" << updateTime;
-#ifndef QT_NO_OPENGL
// Having every repaint() leading to a sync/flush is bad as it causes
// compositing and waiting for vsync each and every time. Change to
// UpdateLater, except for approx. once per frame to prevent starvation in
@@ -392,7 +389,6 @@ void QWidgetRepaintManager::sendUpdateRequest(QWidget *widget, UpdateTime update
updateTime = UpdateLater;
}
}
-#endif
switch (updateTime) {
case UpdateLater:
@@ -571,7 +567,6 @@ bool QWidgetRepaintManager::bltRect(const QRect &rect, int dx, int dy, QWidget *
// ---------------------------------------------------------------------------
-#ifndef QT_NO_OPENGL
static void findTextureWidgetsRecursively(QWidget *tlw, QWidget *widget,
QPlatformTextureList *widgetTextures,
QList<QWidget *> *nativeChildren)
@@ -580,7 +575,7 @@ static void findTextureWidgetsRecursively(QWidget *tlw, QWidget *widget,
if (wd->renderToTexture) {
QPlatformTextureList::Flags flags = wd->textureListFlags();
const QRect rect(widget->mapTo(tlw, QPoint()), widget->size());
- widgetTextures->appendTexture(widget, wd->textureId(), rect, wd->clipRect(), flags);
+ widgetTextures->appendTexture(widget, wd->texture(), rect, wd->clipRect(), flags);
}
for (int i = 0; i < wd->children.size(); ++i) {
@@ -624,33 +619,12 @@ static QPlatformTextureList *widgetTexturesFor(QWidget *tlw, QWidget *widget)
}
}
- if (QWidgetPrivate::get(widget)->textureChildSeen) {
- // No render-to-texture widgets in the (sub-)tree due to hidden or native
- // children. Returning null results in using the normal backingstore flush path
- // without OpenGL-based compositing. This is very desirable normally. However,
- // some platforms cannot handle switching between the non-GL and GL paths for
- // their windows so it has to be opt-in.
- static bool switchableWidgetComposition =
- QGuiApplicationPrivate::instance()->platformIntegration()
- ->hasCapability(QPlatformIntegration::SwitchableWidgetComposition);
- if (!switchableWidgetComposition)
- return qt_dummy_platformTextureList();
- }
+ if (QWidgetPrivate::get(widget)->textureChildSeen)
+ return qt_dummy_platformTextureList();
return nullptr;
}
-#else
-
-static QPlatformTextureList *widgetTexturesFor(QWidget *tlw, QWidget *widget)
-{
- Q_UNUSED(tlw);
- Q_UNUSED(widget);
- return nullptr;
-}
-
-#endif // QT_NO_OPENGL
-
// ---------------------------------------------------------------------------
/*!
@@ -725,7 +699,6 @@ bool QWidgetPrivate::shouldDiscardSyncRequest() const
bool QWidgetRepaintManager::syncAllowed()
{
-#ifndef QT_NO_OPENGL
QTLWExtra *tlwExtra = tlw->d_func()->maybeTopData();
if (textureListWatcher && !textureListWatcher->isLocked()) {
textureListWatcher->deleteLater();
@@ -744,7 +717,6 @@ bool QWidgetRepaintManager::syncAllowed()
if (skipSync) // cannot compose due to widget textures being in use
return false;
}
-#endif
return true;
}
@@ -857,7 +829,6 @@ void QWidgetRepaintManager::paintAndFlush()
}
dirtyWidgets.clear();
-#ifndef QT_NO_OPENGL
// Find all render-to-texture child widgets (including self).
// The search is cut at native widget boundaries, meaning that each native child widget
// has its own list for the subtree below it.
@@ -865,13 +836,12 @@ void QWidgetRepaintManager::paintAndFlush()
tlwExtra->widgetTextures.clear();
findAllTextureWidgetsRecursively(tlw, tlw);
qt_window_private(tlw->windowHandle())->compositing = false; // will get updated in flush()
-#endif
if (toClean.isEmpty()) {
// Nothing to repaint. However renderToTexture widgets are handled
// specially, they are not in the regular dirty list, in order to
// prevent triggering unnecessary backingstore painting when only the
- // OpenGL content changes. Check if we have such widgets in the special
+ // texture content changes. Check if we have such widgets in the special
// dirty list.
QVarLengthArray<QWidget *, 16> paintPending;
const int numPaintPending = dirtyRenderToTextureWidgets.count();
@@ -903,7 +873,6 @@ void QWidgetRepaintManager::paintAndFlush()
return;
}
-#ifndef QT_NO_OPENGL
for (const auto &tl : tlwExtra->widgetTextures) {
for (int i = 0; i < tl->count(); ++i) {
QWidget *w = static_cast<QWidget *>(tl->source(i));
@@ -920,7 +889,6 @@ void QWidgetRepaintManager::paintAndFlush()
for (int i = 0; i < dirtyRenderToTextureWidgets.count(); ++i)
resetWidget(dirtyRenderToTextureWidgets.at(i));
dirtyRenderToTextureWidgets.clear();
-#endif
#if QT_CONFIG(graphicsview)
if (tlw->d_func()->extra->proxyWidget) {
@@ -1046,12 +1014,10 @@ void QWidgetRepaintManager::flush()
// Render-to-texture widgets are not in topLevelNeedsFlush so flush if we have not done it above.
if (!flushed && !hasNeedsFlushWidgets) {
-#ifndef QT_NO_OPENGL
if (!tlw->d_func()->topData()->widgetTextures.empty()) {
if (QPlatformTextureList *widgetTextures = widgetTexturesFor(tlw, tlw))
flush(tlw, QRegion(), widgetTextures);
}
-#endif
}
if (!hasNeedsFlushWidgets)
@@ -1073,12 +1039,7 @@ void QWidgetRepaintManager::flush()
*/
void QWidgetRepaintManager::flush(QWidget *widget, const QRegion &region, QPlatformTextureList *widgetTextures)
{
-#ifdef QT_NO_OPENGL
- Q_UNUSED(widgetTextures);
- Q_ASSERT(!region.isEmpty());
-#else
Q_ASSERT(!region.isEmpty() || widgetTextures);
-#endif
Q_ASSERT(widget);
Q_ASSERT(tlw);
@@ -1091,8 +1052,6 @@ void QWidgetRepaintManager::flush(QWidget *widget, const QRegion &region, QPlatf
return;
}
- qCInfo(lcWidgetPainting) << "Flushing" << region << "of" << widget;
-
static bool fpsDebug = qEnvironmentVariableIntValue("QT_DEBUG_FPS");
if (fpsDebug) {
if (!perfFrames++)
@@ -1108,40 +1067,37 @@ void QWidgetRepaintManager::flush(QWidget *widget, const QRegion &region, QPlatf
if (widget != tlw)
offset += widget->mapTo(tlw, QPoint());
- QRegion effectiveRegion = region;
-#ifndef QT_NO_OPENGL
- const bool compositionWasActive = widget->d_func()->renderToTextureComposeActive;
- if (!widgetTextures) {
- widget->d_func()->renderToTextureComposeActive = false;
- // Detect the case of falling back to the normal flush path when no
- // render-to-texture widgets are visible anymore. We will force one
- // last flush to go through the OpenGL-based composition to prevent
- // artifacts. The next flush after this one will use the normal path.
- if (compositionWasActive)
+ if (widget->d_func()->usesRhiFlush) {
+ QRhi *rhi = store->handle()->rhi();
+ qCDebug(lcWidgetPainting) << "Flushing" << region << "of" << widget
+ << "with QRhi" << rhi
+ << "to window" << widget->windowHandle();
+ if (!widgetTextures)
widgetTextures = qt_dummy_platformTextureList;
- } else {
- widget->d_func()->renderToTextureComposeActive = true;
- }
- // When changing the composition status, make sure the dirty region covers
- // the entire widget. Just having e.g. the shown/hidden render-to-texture
- // widget's area marked as dirty is incorrect when changing flush paths.
- if (compositionWasActive != widget->d_func()->renderToTextureComposeActive)
- effectiveRegion = widget->rect();
-
- // re-test since we may have been forced to this path via the dummy texture list above
- if (widgetTextures) {
+
qt_window_private(tlw->windowHandle())->compositing = true;
- widget->window()->d_func()->sendComposeStatus(widget->window(), false);
+ QWidgetPrivate *widgetWindowPrivate = widget->window()->d_func();
+ widgetWindowPrivate->sendComposeStatus(widget->window(), false);
// A window may have alpha even when the app did not request
// WA_TranslucentBackground. Therefore the compositor needs to know whether the app intends
// to rely on translucency, in order to decide if it should clear to transparent or opaque.
const bool translucentBackground = widget->testAttribute(Qt::WA_TranslucentBackground);
- store->handle()->composeAndFlush(widget->windowHandle(), effectiveRegion, offset,
- widgetTextures, translucentBackground);
- widget->window()->d_func()->sendComposeStatus(widget->window(), true);
- } else
-#endif
- store->flush(effectiveRegion, widget->windowHandle(), offset);
+
+ QPlatformBackingStore::FlushResult flushResult;
+ flushResult = store->handle()->rhiFlush(widget->windowHandle(),
+ region,
+ offset,
+ widgetTextures,
+ translucentBackground);
+ widgetWindowPrivate->sendComposeStatus(widget->window(), true);
+ if (flushResult == QPlatformBackingStore::FlushFailedDueToLostDevice) {
+ store->handle()->graphicsDeviceReportedLost();
+ widget->update();
+ }
+ } else {
+ qCInfo(lcWidgetPainting) << "Flushing" << region << "of" << widget;
+ store->flush(region, widget->windowHandle(), offset);
+ }
}
// ---------------------------------------------------------------------------
@@ -1353,6 +1309,11 @@ void QWidgetPrivate::invalidateBackingStore_resizeHelper(const QPoint &oldPos, c
}
}
+QRhi *QWidgetRepaintManager::rhi() const
+{
+ return store->handle()->rhi();
+}
+
QT_END_NAMESPACE
#include "qwidgetrepaintmanager.moc"
diff --git a/src/widgets/kernel/qwidgetrepaintmanager_p.h b/src/widgets/kernel/qwidgetrepaintmanager_p.h
index 89f65e05bc..fe26d52bcb 100644
--- a/src/widgets/kernel/qwidgetrepaintmanager_p.h
+++ b/src/widgets/kernel/qwidgetrepaintmanager_p.h
@@ -62,8 +62,10 @@ QT_BEGIN_NAMESPACE
class QPlatformTextureList;
class QPlatformTextureListWatcher;
class QWidgetRepaintManager;
+class QRhi;
+class QRhiSwapChain;
-class Q_AUTOTEST_EXPORT QWidgetRepaintManager
+class Q_WIDGETS_EXPORT QWidgetRepaintManager
{
Q_GADGET
public:
@@ -106,6 +108,8 @@ public:
bool bltRect(const QRect &rect, int dx, int dy, QWidget *widget);
+ QRhi *rhi() const;
+
private:
void updateLists(QWidget *widget);
diff --git a/src/widgets/kernel/qwidgetwindow.cpp b/src/widgets/kernel/qwidgetwindow.cpp
index 88818ce9c5..6524393ee1 100644
--- a/src/widgets/kernel/qwidgetwindow.cpp
+++ b/src/widgets/kernel/qwidgetwindow.cpp
@@ -108,9 +108,6 @@ public:
}
QRectF closestAcceptableGeometry(const QRectF &rect) const override;
-#if QT_CONFIG(opengl)
- QOpenGLContext *shareContext() const override;
-#endif
void processSafeAreaMarginsChanged() override
{
@@ -152,26 +149,19 @@ QRectF QWidgetWindowPrivate::closestAcceptableGeometry(const QRectF &rect) const
return result;
}
-#if QT_CONFIG(opengl)
-QOpenGLContext *QWidgetWindowPrivate::shareContext() const
-{
- Q_Q(const QWidgetWindow);
- const QWidgetPrivate *widgetPrivate = QWidgetPrivate::get(q->widget());
- return widgetPrivate->shareContext();
-}
-#endif // opengl
+bool q_evaluateRhiConfig(const QWidget *w, QPlatformBackingStoreRhiConfig *outConfig, QSurface::SurfaceType *outType);
QWidgetWindow::QWidgetWindow(QWidget *widget)
: QWindow(*new QWidgetWindowPrivate(), nullptr)
, m_widget(widget)
{
updateObjectName();
- // Enable QOpenGLWidget/QQuickWidget children if the platform plugin supports it,
- // and the application developer has not explicitly disabled it.
- if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::RasterGLSurface)
- && !QCoreApplication::testAttribute(Qt::AA_ForceRasterWidgets)) {
- setSurfaceType(QSurface::RasterGLSurface);
+ if (!QCoreApplication::testAttribute(Qt::AA_ForceRasterWidgets)) {
+ QSurface::SurfaceType type = QSurface::RasterSurface;
+ q_evaluateRhiConfig(m_widget, nullptr, &type);
+ setSurfaceType(type);
}
+
connect(widget, &QObject::objectNameChanged, this, &QWidgetWindow::updateObjectName);
connect(this, SIGNAL(screenChanged(QScreen*)), this, SLOT(handleScreenChange()));
}
diff --git a/src/widgets/kernel/qwindowcontainer.cpp b/src/widgets/kernel/qwindowcontainer.cpp
index 5484544444..b26bc68d19 100644
--- a/src/widgets/kernel/qwindowcontainer.cpp
+++ b/src/widgets/kernel/qwindowcontainer.cpp
@@ -204,8 +204,6 @@ QWidget *QWidget::createWindowContainer(QWindow *window, QWidget *parent, Qt::Wi
return new QWindowContainer(window, parent, flags);
}
-
-
/*!
\internal
*/
@@ -219,13 +217,6 @@ QWindowContainer::QWindowContainer(QWindow *embeddedWindow, QWidget *parent, Qt:
return;
}
- // The embedded QWindow must use the same logic as QWidget when it comes to the surface type.
- // Otherwise we may end up with BadMatch failures on X11.
- if (embeddedWindow->surfaceType() == QSurface::RasterSurface
- && QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::RasterGLSurface)
- && !QCoreApplication::testAttribute(Qt::AA_ForceRasterWidgets))
- embeddedWindow->setSurfaceType(QSurface::RasterGLSurface);
-
d->window = embeddedWindow;
QString windowName = d->window->objectName();
diff --git a/src/widgets/widgets/qwidgetlinecontrol_p.h b/src/widgets/widgets/qwidgetlinecontrol_p.h
index bb24fe4d5b..b9c8b84e53 100644
--- a/src/widgets/widgets/qwidgetlinecontrol_p.h
+++ b/src/widgets/widgets/qwidgetlinecontrol_p.h
@@ -72,6 +72,9 @@
#include <vector>
#include <memory>
+#ifdef Q_OS_WIN
+# include <qt_windows.h>
+#endif
#ifdef DrawText
# undef DrawText
#endif
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