aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLaszlo Agocs <laszlo.agocs@qt.io>2020-01-15 09:08:24 +0100
committerLaszlo Agocs <laszlo.agocs@qt.io>2020-04-11 18:23:55 +0200
commit400d176760de84626500787674b1ece387b25893 (patch)
treedbb8b5fd50440e6c01d6b940fe1692ba8e6a1f5c
parent654959e2e2b74855036f949a9c23c9d9c7a614ac (diff)
Allow redirecting QRhi-based rendering via QQuickRenderControl
Implement the Qt 6 TODO for using an externally-provided render target when rendering the scene via QRhi. And say hello to QQuickRenderTarget. This class exists to allow potentially extending later what a "render target" consists of. Instead of hard-coding taking a single void * in the setRenderTarget() function, it takes a (implicitly shared, d-pointered) QQuickRenderTarget, which in turn can be created via static factory functions - of which new ones can be added later on. The new version of QQuickWindow::setRenderTarget() takes a QQuickRenderTarget. QQuickRenderControl gets a new initialize() variant, and a few extra functions (beginFrame(), endFrame()). This allows it to, by using QSGRhiSupport internally, create a QRhi under the hood. As a bonus, this also fixes an existing scenegraph resource leak when destroying the QQuickRenderControl. The qquickrendercontrol autotest is extended, with a QRhi-based test case that is executed for all of the QRhi backends that succeed to initialize. This is the internal verification. In addition, there is a Vulkan-based one that creates its own VkDevice, VkImage, and friends, and then uses Qt Quick with the same Vulkan device, targeting the VkImage. This test verifies the typical application use case. (sadly, life is too short to waste it on writing Vulkan boilerplate for an on-screen version of this, but we have the D3D11 example instead) What QQuickRenderControl loses, when used in combination with QRhi, is the grab() function. This never made much sense as a public API: QQuickWindow::grabWindow() call this when the window is associated with a rendercontrol, so as a public API QQuickRenderControl::grab() is redundant, because one gets the same result via the standard QQuickWindow API. It is now made private. More importantly, reading back the content is no longer supported, unless the 'software' backend is in use. The reasoning here is that, if the client of the API manages and provides the render target (as abstracted by QQuickRenderTarget), it is then expected to be capable of reading back the content in whatever way it sees fit, because it owns and manages the resource (e.g. the texture) in the first place. Providing fragile convenience functions for this is not reasonable anymore, and was questionable even with OpenGL, given that it is not future proof - what if the target is suddenly a floating point texture, for instance? The software backend case makes sense because that relies on private APIs - and has no render target concept either - so there the same cannot be achieved by applications by relying on public APIs only. Another new class is QQuickGraphicsDevice. This is very similar to QQuickRenderTarget, it is a simple container capable of holding a set of of native objects, mostly in the form of void*s, with future extensibility thanks to the static factory functions. (examples of native object sets would be a ID3D11Device + ID3D11DeviceContext, or a QOpenGLContext, or a MTLDevice + MTLCommandQueue, or a number of Vulkan device-related objects, etc.) This allows one to specify that the QRhi created under the hood (either by QQuickRenderControl or by the render loop) should use an existing graphics device (i.e. it is basically a public wrapper for values that go into a QRhi*InitParams under the hood). QQuickRenderTarget and QQuickGraphicsDevice are both demonstrated in a new example: rendercontrol_d3d11. We choose D3D11 because it is reasonably simple to set up a renderer with a window, and, because there is known user demand for Qt Quick - external D3D engine interop. Passing in the custom engine's own ID3D11Device and ID3D11DeviceContext is essential: the texture (ID3D11Texture2D) Qt Quick is targeting would not be usable if Qt Quick's QRhi was using a different ID3D11Device. Task-number: QTBUG-78595 Change-Id: I5dfe7f6cf1540daffc2f11136be114a08e87202b Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Andy Nichols <andy.nichols@qt.io>
-rw-r--r--examples/quick/rendercontrol/rendercontrol.pro7
-rw-r--r--examples/quick/rendercontrol/rendercontrol_d3d11/CMakeLists.txt51
-rw-r--r--examples/quick/rendercontrol/rendercontrol_d3d11/buildshaders.bat2
-rw-r--r--examples/quick/rendercontrol/rendercontrol_d3d11/demo.qml208
-rw-r--r--examples/quick/rendercontrol/rendercontrol_d3d11/doc/images/rendercontrol-d3d11-example.jpgbin0 -> 48486 bytes
-rw-r--r--examples/quick/rendercontrol/rendercontrol_d3d11/doc/src/rendercontrol_d3d11.qdoc33
-rw-r--r--examples/quick/rendercontrol/rendercontrol_d3d11/engine.cpp241
-rw-r--r--examples/quick/rendercontrol/rendercontrol_d3d11/engine.h95
-rw-r--r--examples/quick/rendercontrol/rendercontrol_d3d11/main.cpp79
-rw-r--r--examples/quick/rendercontrol/rendercontrol_d3d11/quad.frag19
-rw-r--r--examples/quick/rendercontrol/rendercontrol_d3d11/quad.frag.inc144
-rw-r--r--examples/quick/rendercontrol/rendercontrol_d3d11/quad.vert36
-rw-r--r--examples/quick/rendercontrol/rendercontrol_d3d11/quad.vert.inc166
-rw-r--r--examples/quick/rendercontrol/rendercontrol_d3d11/rendercontrol.qrc5
-rw-r--r--examples/quick/rendercontrol/rendercontrol_d3d11/rendercontrol_d3d11.pro19
-rw-r--r--examples/quick/rendercontrol/rendercontrol_d3d11/window.cpp389
-rw-r--r--examples/quick/rendercontrol/rendercontrol_d3d11/window.h100
-rw-r--r--examples/quick/rendercontrol/rendercontrol_opengl/doc/images/rendercontrol-opengl-example.jpg (renamed from examples/quick/rendercontrol/rendercontrol_opengl/doc/images/rendercontrol-example.jpg)bin44196 -> 44196 bytes
-rw-r--r--examples/quick/rendercontrol/rendercontrol_opengl/doc/src/rendercontrol_opengl.qdoc (renamed from examples/quick/rendercontrol/rendercontrol_opengl/doc/src/rendercontrol.qdoc)4
-rw-r--r--src/quick/CMakeLists.txt2
-rw-r--r--src/quick/items/items.pri10
-rw-r--r--src/quick/items/qquickgraphicsdevice.cpp216
-rw-r--r--src/quick/items/qquickgraphicsdevice.h73
-rw-r--r--src/quick/items/qquickgraphicsdevice_p.h105
-rw-r--r--src/quick/items/qquickrendercontrol.cpp316
-rw-r--r--src/quick/items/qquickrendercontrol.h19
-rw-r--r--src/quick/items/qquickrendercontrol_p.h18
-rw-r--r--src/quick/items/qquickrendertarget.cpp293
-rw-r--r--src/quick/items/qquickrendertarget.h78
-rw-r--r--src/quick/items/qquickrendertarget_p.h90
-rw-r--r--src/quick/items/qquickwindow.cpp295
-rw-r--r--src/quick/items/qquickwindow.h21
-rw-r--r--src/quick/items/qquickwindow_p.h40
-rw-r--r--src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp4
-rw-r--r--src/quick/scenegraph/qsgrenderloop.cpp2
-rw-r--r--src/quick/scenegraph/qsgrhisupport.cpp68
-rw-r--r--src/quick/scenegraph/qsgrhisupport_p.h10
-rw-r--r--src/quickwidgets/qquickwidget.cpp2
-rw-r--r--tests/auto/quick/qquickrendercontrol/tst_qquickrendercontrol.cpp604
39 files changed, 3729 insertions, 135 deletions
diff --git a/examples/quick/rendercontrol/rendercontrol.pro b/examples/quick/rendercontrol/rendercontrol.pro
index cdb431c8fd..d49be63c50 100644
--- a/examples/quick/rendercontrol/rendercontrol.pro
+++ b/examples/quick/rendercontrol/rendercontrol.pro
@@ -1,4 +1,9 @@
TEMPLATE = subdirs
-SUBDIRS = \
+SUBDIRS += \
rendercontrol_opengl
+
+win32 {
+ SUBDIRS += \
+ rendercontrol_d3d11
+}
diff --git a/examples/quick/rendercontrol/rendercontrol_d3d11/CMakeLists.txt b/examples/quick/rendercontrol/rendercontrol_d3d11/CMakeLists.txt
new file mode 100644
index 0000000000..c2f7a1f806
--- /dev/null
+++ b/examples/quick/rendercontrol/rendercontrol_d3d11/CMakeLists.txt
@@ -0,0 +1,51 @@
+# Generated from rendercontrol_d3d11.pro.
+
+cmake_minimum_required(VERSION 3.14)
+project(rendercontrol_d3d11 LANGUAGES CXX)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+set(CMAKE_AUTOUIC ON)
+
+set(INSTALL_EXAMPLEDIR "examples/quick/rendercontrol/rendercontrol_d3d11")
+
+find_package(Qt6 COMPONENTS Core)
+find_package(Qt6 COMPONENTS Gui)
+find_package(Qt6 COMPONENTS Quick)
+find_package(Qt6 COMPONENTS Qml)
+
+add_qt_gui_executable(rendercontrol_d3d11
+ engine.cpp engine.h
+ main.cpp
+ window.cpp window.h
+)
+target_link_libraries(rendercontrol_d3d11 PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Qml
+ Qt::Quick
+ d3d11
+ dxgi
+ dxguid
+)
+
+
+# Resources:
+set(rendercontrol_resource_files
+ "demo.qml"
+)
+
+qt6_add_resources(rendercontrol_d3d11 "rendercontrol"
+ PREFIX
+ "/rendercontrol"
+ FILES
+ ${rendercontrol_resource_files}
+)
+
+install(TARGETS rendercontrol_d3d11
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/quick/rendercontrol/rendercontrol_d3d11/buildshaders.bat b/examples/quick/rendercontrol/rendercontrol_d3d11/buildshaders.bat
new file mode 100644
index 0000000000..aedcde2d59
--- /dev/null
+++ b/examples/quick/rendercontrol/rendercontrol_d3d11/buildshaders.bat
@@ -0,0 +1,2 @@
+fxc /T vs_5_0 /E quad_vs_main /Fh quad.vert.inc quad.vert
+fxc /T ps_5_0 /E quad_ps_main /Fh quad.frag.inc quad.frag
diff --git a/examples/quick/rendercontrol/rendercontrol_d3d11/demo.qml b/examples/quick/rendercontrol/rendercontrol_d3d11/demo.qml
new file mode 100644
index 0000000000..32e2f423d1
--- /dev/null
+++ b/examples/quick/rendercontrol/rendercontrol_d3d11/demo.qml
@@ -0,0 +1,208 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+import QtQuick.Particles 2.0
+
+Rectangle {
+ id: root
+
+ gradient: Gradient {
+ GradientStop { position: 0; color: mouse.pressed ? "lightsteelblue" : "steelblue" }
+ GradientStop { position: 1; color: "black" }
+ }
+
+ Text {
+ anchors.centerIn: parent
+ text: "Qt Quick scene rendered to a ID3D11Texture2D"
+ font.pointSize: 32
+ color: "white"
+
+ SequentialAnimation on rotation {
+ PauseAnimation { duration: 2500 }
+ NumberAnimation { from: 0; to: 360; duration: 5000; easing.type: Easing.InOutCubic }
+ loops: Animation.Infinite
+ }
+ }
+
+ ParticleSystem {
+ id: particles
+ anchors.fill: parent
+
+ ImageParticle {
+ id: smoke
+ system: particles
+ anchors.fill: parent
+ groups: ["A", "B"]
+ source: "qrc:///particleresources/glowdot.png"
+ colorVariation: 0
+ color: "#00111111"
+ }
+ ImageParticle {
+ id: flame
+ anchors.fill: parent
+ system: particles
+ groups: ["C", "D"]
+ source: "qrc:///particleresources/glowdot.png"
+ colorVariation: 0.1
+ color: "#00ff400f"
+ }
+
+ Emitter {
+ id: fire
+ system: particles
+ group: "C"
+
+ y: parent.height
+ width: parent.width
+
+ emitRate: 350
+ lifeSpan: 3500
+
+ acceleration: PointDirection { y: -17; xVariation: 3 }
+ velocity: PointDirection {xVariation: 3}
+
+ size: 24
+ sizeVariation: 8
+ endSize: 4
+ }
+
+ TrailEmitter {
+ id: fireSmoke
+ group: "B"
+ system: particles
+ follow: "C"
+ width: root.width
+ height: root.height - 68
+
+ emitRatePerParticle: 1
+ lifeSpan: 2000
+
+ velocity: PointDirection {y:-17*6; yVariation: -17; xVariation: 3}
+ acceleration: PointDirection {xVariation: 3}
+
+ size: 36
+ sizeVariation: 8
+ endSize: 16
+ }
+
+ TrailEmitter {
+ id: fireballFlame
+ anchors.fill: parent
+ system: particles
+ group: "D"
+ follow: "E"
+
+ emitRatePerParticle: 120
+ lifeSpan: 180
+ emitWidth: TrailEmitter.ParticleSize
+ emitHeight: TrailEmitter.ParticleSize
+ emitShape: EllipseShape{}
+
+ size: 16
+ sizeVariation: 4
+ endSize: 4
+ }
+
+ TrailEmitter {
+ id: fireballSmoke
+ anchors.fill: parent
+ system: particles
+ group: "A"
+ follow: "E"
+
+ emitRatePerParticle: 128
+ lifeSpan: 2400
+ emitWidth: TrailEmitter.ParticleSize
+ emitHeight: TrailEmitter.ParticleSize
+ emitShape: EllipseShape{}
+
+ velocity: PointDirection {yVariation: 16; xVariation: 16}
+ acceleration: PointDirection {y: -16}
+
+ size: 24
+ sizeVariation: 8
+ endSize: 8
+ }
+
+ Emitter {
+ id: balls
+ system: particles
+ group: "E"
+
+ y: parent.height
+ width: parent.width
+
+ emitRate: 2
+ lifeSpan: 7000
+
+ velocity: PointDirection {y:-17*4*2; xVariation: 6*6}
+ acceleration: PointDirection {y: 17*2; xVariation: 6*6}
+
+ size: 8
+ sizeVariation: 4
+ }
+
+ Turbulence { //A bit of turbulence makes the smoke look better
+ anchors.fill: parent
+ groups: ["A","B"]
+ strength: 32
+ system: particles
+ }
+ }
+
+ onWidthChanged: particles.reset()
+ onHeightChanged: particles.reset()
+
+ MouseArea {
+ id: mouse
+ anchors.fill: parent
+ }
+}
diff --git a/examples/quick/rendercontrol/rendercontrol_d3d11/doc/images/rendercontrol-d3d11-example.jpg b/examples/quick/rendercontrol/rendercontrol_d3d11/doc/images/rendercontrol-d3d11-example.jpg
new file mode 100644
index 0000000000..4d56a946cc
--- /dev/null
+++ b/examples/quick/rendercontrol/rendercontrol_d3d11/doc/images/rendercontrol-d3d11-example.jpg
Binary files differ
diff --git a/examples/quick/rendercontrol/rendercontrol_d3d11/doc/src/rendercontrol_d3d11.qdoc b/examples/quick/rendercontrol/rendercontrol_d3d11/doc/src/rendercontrol_d3d11.qdoc
new file mode 100644
index 0000000000..63f5477ff4
--- /dev/null
+++ b/examples/quick/rendercontrol/rendercontrol_d3d11/doc/src/rendercontrol_d3d11.qdoc
@@ -0,0 +1,33 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** 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 Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \title QQuickRenderControl D3D11 Example
+ \example rendercontrol/rendercontrol_d3d11
+ \brief Shows how to render a Qt Quick scene into a texture that is then used by a non-Quick based Direct3D 11 renderer.
+ \image rendercontrol-d3d11-example.jpg
+*/
diff --git a/examples/quick/rendercontrol/rendercontrol_d3d11/engine.cpp b/examples/quick/rendercontrol/rendercontrol_d3d11/engine.cpp
new file mode 100644
index 0000000000..be1f6d842c
--- /dev/null
+++ b/examples/quick/rendercontrol/rendercontrol_d3d11/engine.cpp
@@ -0,0 +1,241 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 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 "engine.h"
+#include <QLibrary>
+#include <comdef.h>
+
+#define ENABLE_DEBUG_LAYER
+
+Engine::~Engine()
+{
+ RELEASE(m_context);
+ RELEASE(m_device);
+ RELEASE(m_dxgiFactory);
+}
+
+QString comErrorMessage(HRESULT hr)
+{
+#ifndef Q_OS_WINRT
+ const _com_error comError(hr);
+#else
+ const _com_error comError(hr, nullptr);
+#endif
+ QString result = QLatin1String("Error 0x") + QString::number(ulong(hr), 16);
+ if (const wchar_t *msg = comError.ErrorMessage())
+ result += QLatin1String(": ") + QString::fromWCharArray(msg);
+ return result;
+}
+
+bool Engine::create()
+{
+ using PtrCreateDXGIFactory2 = HRESULT (WINAPI *)(UINT, REFIID, void **);
+ QLibrary dxgilib(QStringLiteral("dxgi"));
+ if (auto createDXGIFactory2 = reinterpret_cast<PtrCreateDXGIFactory2>(dxgilib.resolve("CreateDXGIFactory2"))) {
+ const HRESULT hr = createDXGIFactory2(0, IID_IDXGIFactory2, reinterpret_cast<void **>(&m_dxgiFactory));
+ if (FAILED(hr)) {
+ qWarning("CreateDXGIFactory2() failed to create DXGI factory: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ } else {
+ qWarning("Unable to resolve CreateDXGIFactory2()");
+ return false;
+ }
+
+ ID3D11DeviceContext *ctx = nullptr;
+ uint flags = 0;
+#ifdef ENABLE_DEBUG_LAYER
+ flags |= D3D11_CREATE_DEVICE_DEBUG;
+#endif
+ // use the default hardware adapter
+ HRESULT hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, flags,
+ nullptr, 0, D3D11_SDK_VERSION,
+ &m_device, &m_featureLevel, &ctx);
+ if (FAILED(hr)) {
+ qWarning("Failed to create D3D11 device and context: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ if (SUCCEEDED(ctx->QueryInterface(IID_ID3D11DeviceContext1, reinterpret_cast<void **>(&m_context)))) {
+ ctx->Release();
+ } else {
+ qWarning("ID3D11DeviceContext1 not supported");
+ return false;
+ }
+
+ return true;
+}
+
+QSize Engine::swapchainSizeForWindow(QWindow *window) const
+{
+ const QSize size = window->size() * window->devicePixelRatio();
+ return QSize(qMax(8, size.width()), qMax(8, size.height()));
+}
+
+Swapchain Engine::createSwapchain(QWindow *window)
+{
+ Swapchain sc = {};
+ const HWND hwnd = reinterpret_cast<HWND>(window->winId());
+ const QSize pixelSize = swapchainSizeForWindow(window);
+
+ // only care about flip discard swapchains here; the old stuff (discard) is
+ // not supported in this example
+
+ DXGI_SWAP_CHAIN_DESC1 desc = {};
+ desc.Width = UINT(pixelSize.width());
+ desc.Height = UINT(pixelSize.height());
+ desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+ desc.SampleDesc.Count = 1;
+ desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+ desc.BufferCount = 2;
+ desc.Scaling = DXGI_SCALING_STRETCH;
+ desc.SwapEffect = DXGI_SWAP_EFFECT(4); // DXGI_SWAP_EFFECT_FLIP_DISCARD
+
+ IDXGISwapChain1 *swapchain = nullptr;
+ HRESULT hr = static_cast<IDXGIFactory2 *>(m_dxgiFactory)->CreateSwapChainForHwnd(m_device, hwnd, &desc,
+ nullptr, nullptr, &swapchain);
+ if (FAILED(hr)) {
+ qWarning("Failed to create D3D11 swapchain: %s", qPrintable(comErrorMessage(hr)));
+ return sc;
+ }
+
+ sc.swapchain = swapchain;
+ sc.pixelSize = pixelSize;
+ createSwapchainBuffers(&sc);
+ return sc;
+}
+
+void Engine::createSwapchainBuffers(Swapchain *sc)
+{
+ ID3D11Texture2D *tex = nullptr;
+ HRESULT hr = sc->swapchain->GetBuffer(0, IID_ID3D11Texture2D, reinterpret_cast<void **>(&tex));
+ if (FAILED(hr)) {
+ qWarning("Failed to query swapchain backbuffer: %s", qPrintable(comErrorMessage(hr)));
+ return;
+ }
+
+ ID3D11RenderTargetView *rtv = nullptr;
+ D3D11_RENDER_TARGET_VIEW_DESC rtvDesc = {};
+ rtvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+ rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
+ hr = m_device->CreateRenderTargetView(tex, &rtvDesc, &rtv);
+ if (FAILED(hr)) {
+ qWarning("Failed to create rtv for swapchain backbuffer: %s", qPrintable(comErrorMessage(hr)));
+ tex->Release();
+ return;
+ }
+
+ D3D11_TEXTURE2D_DESC texDesc = {};
+ texDesc.Width = UINT(sc->pixelSize.width());
+ texDesc.Height = UINT(sc->pixelSize.height());
+ texDesc.MipLevels = 1;
+ texDesc.ArraySize = 1;
+ texDesc.SampleDesc.Count = 1;
+ texDesc.Usage = D3D11_USAGE_DEFAULT;
+ texDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
+ texDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
+
+ ID3D11Texture2D *ds = nullptr;
+ hr = m_device->CreateTexture2D(&texDesc, nullptr, &ds);
+ if (FAILED(hr)) {
+ qWarning("Failed to create depth-stencil buffer: %s", qPrintable(comErrorMessage(hr)));
+ tex->Release();
+ rtv->Release();
+ return;
+ }
+
+ ID3D11DepthStencilView *dsv = nullptr;
+ D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc = {};
+ dsvDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
+ dsvDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
+ hr = m_device->CreateDepthStencilView(ds, &dsvDesc, &dsv);
+ if (FAILED(hr)) {
+ qWarning("Failed to create dsv: %s", qPrintable(comErrorMessage(hr)));
+ tex->Release();
+ rtv->Release();
+ ds->Release();
+ return;
+ }
+
+ sc->tex = tex;
+ sc->rtv = rtv;
+ sc->ds = ds;
+ sc->dsv = dsv;
+}
+
+void Engine::resizeSwapchain(Swapchain *sc, QWindow *window)
+{
+ const QSize pixelSize = swapchainSizeForWindow(window);
+
+ RELEASE(sc->dsv);
+ RELEASE(sc->ds);
+ RELEASE(sc->rtv);
+ RELEASE(sc->tex);
+
+ HRESULT hr = sc->swapchain->ResizeBuffers(2, UINT(pixelSize.width()), UINT(pixelSize.height()),
+ DXGI_FORMAT_R8G8B8A8_UNORM, 0);
+ if (FAILED(hr)) {
+ qWarning("Failed to resize D3D11 swapchain: %s", qPrintable(comErrorMessage(hr)));
+ return;
+ }
+
+ sc->pixelSize = pixelSize;
+ createSwapchainBuffers(sc);
+}
+
+void Swapchain::destroy()
+{
+ RELEASE(dsv);
+ RELEASE(ds);
+ RELEASE(rtv);
+ RELEASE(tex);
+ RELEASE(swapchain);
+ pixelSize = QSize();
+}
diff --git a/examples/quick/rendercontrol/rendercontrol_d3d11/engine.h b/examples/quick/rendercontrol/rendercontrol_d3d11/engine.h
new file mode 100644
index 0000000000..8b1b3ebf39
--- /dev/null
+++ b/examples/quick/rendercontrol/rendercontrol_d3d11/engine.h
@@ -0,0 +1,95 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 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 ENGINE_H
+#define ENGINE_H
+
+#include <QWindow>
+
+#include <d3d11_1.h>
+#include <dxgi1_3.h>
+
+#define RELEASE(obj) { if (obj) { obj->Release(); obj = nullptr; } }
+
+QString comErrorMessage(HRESULT hr);
+
+struct Swapchain
+{
+ IDXGISwapChain1 *swapchain;
+ ID3D11Texture2D *tex;
+ ID3D11RenderTargetView *rtv;
+ ID3D11Texture2D *ds;
+ ID3D11DepthStencilView *dsv;
+ QSize pixelSize;
+
+ void destroy();
+};
+
+class Engine
+{
+public:
+ ~Engine();
+ bool create();
+ QSize swapchainSizeForWindow(QWindow *window) const;
+ Swapchain createSwapchain(QWindow *window);
+ void resizeSwapchain(Swapchain *sc, QWindow *window);
+ ID3D11Device *device() { return m_device; }
+ ID3D11DeviceContext1 *context() { return m_context; }
+
+private:
+ void createSwapchainBuffers(Swapchain *sc);
+
+ IDXGIFactory1 *m_dxgiFactory = nullptr;
+ ID3D11Device *m_device = nullptr;
+ ID3D11DeviceContext1 *m_context = nullptr;
+ D3D_FEATURE_LEVEL m_featureLevel;
+};
+
+#endif
diff --git a/examples/quick/rendercontrol/rendercontrol_d3d11/main.cpp b/examples/quick/rendercontrol/rendercontrol_d3d11/main.cpp
new file mode 100644
index 0000000000..f9a1dbb75f
--- /dev/null
+++ b/examples/quick/rendercontrol/rendercontrol_d3d11/main.cpp
@@ -0,0 +1,79 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 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$
+**
+****************************************************************************/
+
+// This example is only functional on Windows with Direct 3D 11.
+
+#include <QGuiApplication>
+#include <QQuickWindow>
+#include "engine.h"
+#include "window.h"
+
+int main(int argc, char **argv)
+{
+ QGuiApplication app(argc, argv);
+
+ QCoreApplication::setApplicationName("Qt Render Control D3D11 Example");
+ QCoreApplication::setOrganizationName("QtProject");
+ QCoreApplication::setApplicationVersion(QT_VERSION_STR);
+
+ // only functional when Qt Quick is also using D3D11
+ QQuickWindow::setSceneGraphBackend(QSGRendererInterface::Direct3D11Rhi);
+
+ Engine engine;
+ if (!engine.create())
+ qFatal("Failed to initialize D3D (this example requires D3D 11.1 and DXGI 1.3)");
+
+ Window window(&engine);
+
+ window.resize(1024, 768);
+ window.show();
+
+ return app.exec();
+}
diff --git a/examples/quick/rendercontrol/rendercontrol_d3d11/quad.frag b/examples/quick/rendercontrol/rendercontrol_d3d11/quad.frag
new file mode 100644
index 0000000000..09914ccdbe
--- /dev/null
+++ b/examples/quick/rendercontrol/rendercontrol_d3d11/quad.frag
@@ -0,0 +1,19 @@
+struct PSIn
+{
+ float2 coord : TEXCOORD0;
+};
+
+struct PSOut
+{
+ float4 color : SV_Target;
+};
+
+Texture2D tex : register(t0);
+SamplerState samp : register(s0);
+
+PSOut quad_ps_main(PSIn input)
+{
+ PSOut output;
+ output.color = tex.Sample(samp, input.coord);
+ return output;
+}
diff --git a/examples/quick/rendercontrol/rendercontrol_d3d11/quad.frag.inc b/examples/quick/rendercontrol/rendercontrol_d3d11/quad.frag.inc
new file mode 100644
index 0000000000..495db447dd
--- /dev/null
+++ b/examples/quick/rendercontrol/rendercontrol_d3d11/quad.frag.inc
@@ -0,0 +1,144 @@
+#if 0
+//
+// Generated by Microsoft (R) HLSL Shader Compiler 10.1
+//
+//
+// Resource Bindings:
+//
+// Name Type Format Dim HLSL Bind Count
+// ------------------------------ ---------- ------- ----------- -------------- ------
+// samp sampler NA NA s0 1
+// tex texture float4 2d t0 1
+//
+//
+//
+// Input signature:
+//
+// Name Index Mask Register SysValue Format Used
+// -------------------- ----- ------ -------- -------- ------- ------
+// TEXCOORD 0 xy 0 NONE float xy
+//
+//
+// Output signature:
+//
+// Name Index Mask Register SysValue Format Used
+// -------------------- ----- ------ -------- -------- ------- ------
+// SV_Target 0 xyzw 0 TARGET float xyzw
+//
+ps_5_0
+dcl_globalFlags refactoringAllowed
+dcl_sampler s0, mode_default
+dcl_resource_texture2d (float,float,float,float) t0
+dcl_input_ps linear v0.xy
+dcl_output o0.xyzw
+sample_indexable(texture2d)(float,float,float,float) o0.xyzw, v0.xyxx, t0.xyzw, s0
+ret
+// Approximately 2 instruction slots used
+#endif
+
+const BYTE g_quad_ps_main[] =
+{
+ 68, 88, 66, 67, 206, 95,
+ 68, 205, 198, 6, 36, 106,
+ 90, 50, 92, 169, 136, 220,
+ 65, 219, 1, 0, 0, 0,
+ 104, 2, 0, 0, 5, 0,
+ 0, 0, 52, 0, 0, 0,
+ 236, 0, 0, 0, 32, 1,
+ 0, 0, 84, 1, 0, 0,
+ 204, 1, 0, 0, 82, 68,
+ 69, 70, 176, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 2, 0, 0, 0,
+ 60, 0, 0, 0, 0, 5,
+ 255, 255, 0, 1, 0, 0,
+ 133, 0, 0, 0, 82, 68,
+ 49, 49, 60, 0, 0, 0,
+ 24, 0, 0, 0, 32, 0,
+ 0, 0, 40, 0, 0, 0,
+ 36, 0, 0, 0, 12, 0,
+ 0, 0, 0, 0, 0, 0,
+ 124, 0, 0, 0, 3, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 1, 0,
+ 0, 0, 129, 0, 0, 0,
+ 2, 0, 0, 0, 5, 0,
+ 0, 0, 4, 0, 0, 0,
+ 255, 255, 255, 255, 0, 0,
+ 0, 0, 1, 0, 0, 0,
+ 13, 0, 0, 0, 115, 97,
+ 109, 112, 0, 116, 101, 120,
+ 0, 77, 105, 99, 114, 111,
+ 115, 111, 102, 116, 32, 40,
+ 82, 41, 32, 72, 76, 83,
+ 76, 32, 83, 104, 97, 100,
+ 101, 114, 32, 67, 111, 109,
+ 112, 105, 108, 101, 114, 32,
+ 49, 48, 46, 49, 0, 171,
+ 171, 171, 73, 83, 71, 78,
+ 44, 0, 0, 0, 1, 0,
+ 0, 0, 8, 0, 0, 0,
+ 32, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 3, 0, 0, 0, 0, 0,
+ 0, 0, 3, 3, 0, 0,
+ 84, 69, 88, 67, 79, 79,
+ 82, 68, 0, 171, 171, 171,
+ 79, 83, 71, 78, 44, 0,
+ 0, 0, 1, 0, 0, 0,
+ 8, 0, 0, 0, 32, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 3, 0,
+ 0, 0, 0, 0, 0, 0,
+ 15, 0, 0, 0, 83, 86,
+ 95, 84, 97, 114, 103, 101,
+ 116, 0, 171, 171, 83, 72,
+ 69, 88, 112, 0, 0, 0,
+ 80, 0, 0, 0, 28, 0,
+ 0, 0, 106, 8, 0, 1,
+ 90, 0, 0, 3, 0, 96,
+ 16, 0, 0, 0, 0, 0,
+ 88, 24, 0, 4, 0, 112,
+ 16, 0, 0, 0, 0, 0,
+ 85, 85, 0, 0, 98, 16,
+ 0, 3, 50, 16, 16, 0,
+ 0, 0, 0, 0, 101, 0,
+ 0, 3, 242, 32, 16, 0,
+ 0, 0, 0, 0, 69, 0,
+ 0, 139, 194, 0, 0, 128,
+ 67, 85, 21, 0, 242, 32,
+ 16, 0, 0, 0, 0, 0,
+ 70, 16, 16, 0, 0, 0,
+ 0, 0, 70, 126, 16, 0,
+ 0, 0, 0, 0, 0, 96,
+ 16, 0, 0, 0, 0, 0,
+ 62, 0, 0, 1, 83, 84,
+ 65, 84, 148, 0, 0, 0,
+ 2, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0
+};
diff --git a/examples/quick/rendercontrol/rendercontrol_d3d11/quad.vert b/examples/quick/rendercontrol/rendercontrol_d3d11/quad.vert
new file mode 100644
index 0000000000..a6ba07f686
--- /dev/null
+++ b/examples/quick/rendercontrol/rendercontrol_d3d11/quad.vert
@@ -0,0 +1,36 @@
+struct VSIn
+{
+ uint id : SV_VertexId;
+};
+
+struct VSOut
+{
+ float2 coord : TEXCOORD0;
+ float4 pos : SV_Position;
+};
+
+static const float2 quadPos[6] = {
+ float2(-0.5, 0.5),
+ float2(0.5, -0.5),
+ float2(-0.5, -0.5),
+ float2(0.5, 0.5),
+ float2(0.5, -0.5),
+ float2(-0.5, 0.5)
+};
+
+static const float2 quadUv[6] = {
+ float2(0.0, 0.0),
+ float2(1.0, 1.0),
+ float2(0.0, 1.0),
+ float2(1.0, 0.0),
+ float2(1.0, 1.0),
+ float2(0.0, 0.0),
+};
+
+VSOut quad_vs_main(VSIn input)
+{
+ VSOut output;
+ output.pos = float4(quadPos[input.id].xy, 0.0, 1.0);
+ output.coord = quadUv[input.id];
+ return output;
+}
diff --git a/examples/quick/rendercontrol/rendercontrol_d3d11/quad.vert.inc b/examples/quick/rendercontrol/rendercontrol_d3d11/quad.vert.inc
new file mode 100644
index 0000000000..6af657f775
--- /dev/null
+++ b/examples/quick/rendercontrol/rendercontrol_d3d11/quad.vert.inc
@@ -0,0 +1,166 @@
+#if 0
+//
+// Generated by Microsoft (R) HLSL Shader Compiler 10.1
+//
+//
+//
+// Input signature:
+//
+// Name Index Mask Register SysValue Format Used
+// -------------------- ----- ------ -------- -------- ------- ------
+// SV_VertexId 0 x 0 VERTID uint x
+//
+//
+// Output signature:
+//
+// Name Index Mask Register SysValue Format Used
+// -------------------- ----- ------ -------- -------- ------- ------
+// TEXCOORD 0 xy 0 NONE float xy
+// SV_Position 0 xyzw 1 POS float xyzw
+//
+vs_5_0
+dcl_globalFlags refactoringAllowed
+dcl_immediateConstantBuffer { { -0.500000, 0.500000, 0, 0},
+ { 0.500000, -0.500000, 1.000000, 1.000000},
+ { -0.500000, -0.500000, 0, 1.000000},
+ { 0.500000, 0.500000, 1.000000, 0},
+ { 0.500000, -0.500000, 1.000000, 1.000000},
+ { -0.500000, 0.500000, 0, 0} }
+dcl_input_sgv v0.x, vertex_id
+dcl_output o0.xy
+dcl_output_siv o1.xyzw, position
+dcl_temps 1
+mov r0.x, v0.x
+mov o0.xy, icb[r0.x + 0].zwzz
+mov o1.xy, icb[r0.x + 0].xyxx
+mov o1.zw, l(0,0,0,1.000000)
+ret
+// Approximately 5 instruction slots used
+#endif
+
+const BYTE g_quad_vs_main[] =
+{
+ 68, 88, 66, 67, 34, 115,
+ 52, 103, 138, 223, 96, 45,
+ 170, 110, 183, 248, 246, 24,
+ 17, 250, 1, 0, 0, 0,
+ 224, 2, 0, 0, 5, 0,
+ 0, 0, 52, 0, 0, 0,
+ 160, 0, 0, 0, 212, 0,
+ 0, 0, 44, 1, 0, 0,
+ 68, 2, 0, 0, 82, 68,
+ 69, 70, 100, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 60, 0, 0, 0, 0, 5,
+ 254, 255, 0, 1, 0, 0,
+ 60, 0, 0, 0, 82, 68,
+ 49, 49, 60, 0, 0, 0,
+ 24, 0, 0, 0, 32, 0,
+ 0, 0, 40, 0, 0, 0,
+ 36, 0, 0, 0, 12, 0,
+ 0, 0, 0, 0, 0, 0,
+ 77, 105, 99, 114, 111, 115,
+ 111, 102, 116, 32, 40, 82,
+ 41, 32, 72, 76, 83, 76,
+ 32, 83, 104, 97, 100, 101,
+ 114, 32, 67, 111, 109, 112,
+ 105, 108, 101, 114, 32, 49,
+ 48, 46, 49, 0, 73, 83,
+ 71, 78, 44, 0, 0, 0,
+ 1, 0, 0, 0, 8, 0,
+ 0, 0, 32, 0, 0, 0,
+ 0, 0, 0, 0, 6, 0,
+ 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1,
+ 0, 0, 83, 86, 95, 86,
+ 101, 114, 116, 101, 120, 73,
+ 100, 0, 79, 83, 71, 78,
+ 80, 0, 0, 0, 2, 0,
+ 0, 0, 8, 0, 0, 0,
+ 56, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 3, 0, 0, 0, 0, 0,
+ 0, 0, 3, 12, 0, 0,
+ 65, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0,
+ 3, 0, 0, 0, 1, 0,
+ 0, 0, 15, 0, 0, 0,
+ 84, 69, 88, 67, 79, 79,
+ 82, 68, 0, 83, 86, 95,
+ 80, 111, 115, 105, 116, 105,
+ 111, 110, 0, 171, 171, 171,
+ 83, 72, 69, 88, 16, 1,
+ 0, 0, 80, 0, 1, 0,
+ 68, 0, 0, 0, 106, 8,
+ 0, 1, 53, 24, 0, 0,
+ 26, 0, 0, 0, 0, 0,
+ 0, 191, 0, 0, 0, 63,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 63,
+ 0, 0, 0, 191, 0, 0,
+ 128, 63, 0, 0, 128, 63,
+ 0, 0, 0, 191, 0, 0,
+ 0, 191, 0, 0, 0, 0,
+ 0, 0, 128, 63, 0, 0,
+ 0, 63, 0, 0, 0, 63,
+ 0, 0, 128, 63, 0, 0,
+ 0, 0, 0, 0, 0, 63,
+ 0, 0, 0, 191, 0, 0,
+ 128, 63, 0, 0, 128, 63,
+ 0, 0, 0, 191, 0, 0,
+ 0, 63, 0, 0, 0, 0,
+ 0, 0, 0, 0, 96, 0,
+ 0, 4, 18, 16, 16, 0,
+ 0, 0, 0, 0, 6, 0,
+ 0, 0, 101, 0, 0, 3,
+ 50, 32, 16, 0, 0, 0,
+ 0, 0, 103, 0, 0, 4,
+ 242, 32, 16, 0, 1, 0,
+ 0, 0, 1, 0, 0, 0,
+ 104, 0, 0, 2, 1, 0,
+ 0, 0, 54, 0, 0, 5,
+ 18, 0, 16, 0, 0, 0,
+ 0, 0, 10, 16, 16, 0,
+ 0, 0, 0, 0, 54, 0,
+ 0, 6, 50, 32, 16, 0,
+ 0, 0, 0, 0, 230, 154,
+ 144, 0, 10, 0, 16, 0,
+ 0, 0, 0, 0, 54, 0,
+ 0, 6, 50, 32, 16, 0,
+ 1, 0, 0, 0, 70, 144,
+ 144, 0, 10, 0, 16, 0,
+ 0, 0, 0, 0, 54, 0,
+ 0, 8, 194, 32, 16, 0,
+ 1, 0, 0, 0, 2, 64,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 128, 63,
+ 62, 0, 0, 1, 83, 84,
+ 65, 84, 148, 0, 0, 0,
+ 5, 0, 0, 0, 1, 0,
+ 0, 0, 6, 0, 0, 0,
+ 3, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 4, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0
+};
diff --git a/examples/quick/rendercontrol/rendercontrol_d3d11/rendercontrol.qrc b/examples/quick/rendercontrol/rendercontrol_d3d11/rendercontrol.qrc
new file mode 100644
index 0000000000..2246eeb842
--- /dev/null
+++ b/examples/quick/rendercontrol/rendercontrol_d3d11/rendercontrol.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/rendercontrol">
+ <file>demo.qml</file>
+ </qresource>
+</RCC>
diff --git a/examples/quick/rendercontrol/rendercontrol_d3d11/rendercontrol_d3d11.pro b/examples/quick/rendercontrol/rendercontrol_d3d11/rendercontrol_d3d11.pro
new file mode 100644
index 0000000000..29cee7580b
--- /dev/null
+++ b/examples/quick/rendercontrol/rendercontrol_d3d11/rendercontrol_d3d11.pro
@@ -0,0 +1,19 @@
+TEMPLATE = app
+
+QT += quick qml
+
+SOURCES += \
+ main.cpp \
+ window.cpp \
+ engine.cpp
+
+HEADERS += \
+ window.h \
+ engine.h
+
+RESOURCES += rendercontrol.qrc
+
+LIBS += -ld3d11 -ldxgi -ldxguid
+
+target.path = $$[QT_INSTALL_EXAMPLES]/quick/rendercontrol/rendercontrol_d3d11
+INSTALLS += target
diff --git a/examples/quick/rendercontrol/rendercontrol_d3d11/window.cpp b/examples/quick/rendercontrol/rendercontrol_d3d11/window.cpp
new file mode 100644
index 0000000000..cb84371fed
--- /dev/null
+++ b/examples/quick/rendercontrol/rendercontrol_d3d11/window.cpp
@@ -0,0 +1,389 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 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 "window.h"
+#include <QCoreApplication>
+#include <QMouseEvent>
+#include <QQuickRenderControl>
+#include <QQuickWindow>
+#include <QQuickItem>
+#include <QQmlEngine>
+#include <QQmlComponent>
+#include <QQuickRenderTarget>
+#include <QQuickGraphicsDevice>
+
+#include "quad.vert.inc"
+#include "quad.frag.inc"
+
+// In this example the Qt Quick scene will always render at 720p regardless of
+// the window size.
+const int QML_WIDTH = 1280;
+const int QML_HEIGHT = 720;
+
+// Set to 4 or 8 to enable MSAA. This will lead to creating a multisample
+// texture, passing in the sample count to Qt Quick (so it sets the graphics
+// pipelines up as appropriate), and then doing a resolve to a non-multisample
+// texture every time Quick has rendered its content.
+const int SAMPLE_COUNT = 1;
+
+// by subclassing QQuickRenderControl we gain the ability to report a QWindow
+// to which certain operations, such as the querying of devicePixelRatio()
+// should be redirected
+class RenderControl : public QQuickRenderControl
+{
+public:
+ RenderControl(QWindow *w) : m_window(w) { }
+ QWindow *renderWindow(QPoint *offset) override;
+
+private:
+ QWindow *m_window;
+};
+
+QWindow *RenderControl::renderWindow(QPoint *offset)
+{
+ if (offset)
+ *offset = QPoint(0, 0);
+ return m_window;
+}
+
+Window::Window(Engine *engine)
+ : m_engine(engine)
+{
+ setSurfaceType(QSurface::OpenGLSurface);
+
+ m_renderControl = new RenderControl(this);
+
+ // Whenever something changed in the Quick scene, or rendering was
+ // requested via others means (e.g. QQuickWindow::update()), it should
+ // trigger rendering into the texture when preparing the next frame.
+ connect(m_renderControl, &QQuickRenderControl::renderRequested, this, [this] { m_quickDirty = true; });
+ connect(m_renderControl, &QQuickRenderControl::sceneChanged, this, [this] { m_quickDirty = true; });
+
+ // Note that on its own this is not sufficient to get MSAA, the render
+ // target (the texture in this case) must be set up accordingly as well,
+ // and the sample count also needs to be passed to
+ // QQuickRenderTarget::fromNativeTexture().
+ m_renderControl->setSamples(SAMPLE_COUNT);
+
+ // Create a QQuickWindow that is associated with out render control. Note that this
+ // window never gets created or shown, meaning that it will never get an underlying
+ // native (platform) window.
+ m_quickWindow = new QQuickWindow(m_renderControl);
+
+ m_qmlEngine = new QQmlEngine;
+ m_qmlComponent = new QQmlComponent(m_qmlEngine, QUrl(QLatin1String("qrc:/rendercontrol/demo.qml")));
+ if (m_qmlComponent->isError()) {
+ for (const QQmlError &error : m_qmlComponent->errors())
+ qWarning() << error.url() << error.line() << error;
+ }
+
+ QObject *rootObject = m_qmlComponent->create();
+ if (m_qmlComponent->isError()) {
+ for (const QQmlError &error : m_qmlComponent->errors())
+ qWarning() << error.url() << error.line() << error;
+ }
+
+ QQuickItem *rootItem = qobject_cast<QQuickItem *>(rootObject);
+ rootItem->setSize(QSize(QML_WIDTH, QML_HEIGHT));
+ m_quickWindow->contentItem()->setSize(rootItem->size());
+ m_quickWindow->setGeometry(0, 0, rootItem->width(), rootItem->height());
+
+ rootItem->setParentItem(m_quickWindow->contentItem());
+}
+
+Window::~Window()
+{
+ delete m_qmlComponent;
+ delete m_qmlEngine;
+ delete m_quickWindow;
+ delete m_renderControl;
+
+ releaseResources();
+
+ // Often a no-op (if we already got SurfaceAboutToBeDestroyed), but there
+ // are cases when that's not sent.
+ m_swapchain.destroy();
+}
+
+// Expose (and UpdateRequest) are all the events we need: resize and screen dpr
+// changes are handled implicitly since every render() checks for size changes
+// so no separate event handlers are needed for that.
+
+void Window::exposeEvent(QExposeEvent *)
+{
+ if (isExposed()) {
+ // initialize if this is the first expose
+ if (!m_swapchain.swapchain)
+ m_swapchain = m_engine->createSwapchain(this);
+ // must always render and present a frame on expose
+ if (!size().isEmpty())
+ render();
+ }
+}
+
+// Input is severly limited in this example: there is no mapping or projection
+// of any kind, so it all behaves as if the Qt Quick content was covering the
+// entire window. The example only cares about button down/up, not the position
+// so this is acceptable here.
+
+void Window::mousePressEvent(QMouseEvent *e)
+{
+ QMouseEvent mappedEvent(e->type(), e->localPos(), e->screenPos(), e->button(), e->buttons(), e->modifiers());
+ QCoreApplication::sendEvent(m_quickWindow, &mappedEvent);
+}
+
+void Window::mouseReleaseEvent(QMouseEvent *e)
+{
+ QMouseEvent mappedEvent(e->type(), e->localPos(), e->screenPos(), e->button(), e->buttons(), e->modifiers());
+ QCoreApplication::sendEvent(m_quickWindow, &mappedEvent);
+}
+
+bool Window::event(QEvent *e)
+{
+ switch (e->type()) {
+ case QEvent::UpdateRequest:
+ render();
+ break;
+
+ case QEvent::PlatformSurface:
+ // trying to be nice, not strictly required for D3D
+ if (static_cast<QPlatformSurfaceEvent *>(e)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed)
+ m_swapchain.destroy();
+ break;
+
+ default:
+ break;
+ }
+
+ return QWindow::event(e);
+}
+
+bool Window::initResources()
+{
+ ID3D11Device *dev = m_engine->device();
+
+ // vertex and pixel shader to render a textured quad
+
+ HRESULT hr = dev->CreateVertexShader(g_quad_vs_main, sizeof(g_quad_vs_main), nullptr, &m_res.vertexShader);
+ if (FAILED(hr)) {
+ qWarning("Failed to create vertex shader: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ hr = dev->CreatePixelShader(g_quad_ps_main, sizeof(g_quad_ps_main), nullptr, &m_res.pixelShader);
+ if (FAILED(hr)) {
+ qWarning("Failed to create pixel shader: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ // texture into which Qt Quick will render and which we will then sample
+
+ D3D11_TEXTURE2D_DESC texDesc = {};
+ texDesc.Width = QML_WIDTH;
+ texDesc.Height = QML_HEIGHT;
+ texDesc.MipLevels = 1;
+ texDesc.ArraySize = 1;
+ texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+ if (SAMPLE_COUNT > 1) {
+ texDesc.SampleDesc.Count = SAMPLE_COUNT;
+ texDesc.SampleDesc.Quality = UINT(D3D11_STANDARD_MULTISAMPLE_PATTERN);
+ } else {
+ texDesc.SampleDesc.Count = 1;
+ }
+ // we have to use BIND_SHADER_RESOURCE even if the texture is MSAA because
+ // an SRV may still get created internally by Qt Quick
+ texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
+ texDesc.Usage = D3D11_USAGE_DEFAULT;
+
+ hr = dev->CreateTexture2D(&texDesc, nullptr, &m_res.texture);
+ if (FAILED(hr)) {
+ qWarning("Failed to create texture: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ if (SAMPLE_COUNT > 1) {
+ texDesc.SampleDesc.Count = 1;
+ texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
+ hr = dev->CreateTexture2D(&texDesc, nullptr, &m_res.resolveTexture);
+ if (FAILED(hr)) {
+ qWarning("Failed to create resolve texture: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ }
+
+ D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
+ srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+ srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
+ srvDesc.Texture2D.MipLevels = 1;
+
+ hr = dev->CreateShaderResourceView(SAMPLE_COUNT > 1 ? m_res.resolveTexture : m_res.texture, &srvDesc, &m_res.textureSrv);
+ if (FAILED(hr)) {
+ qWarning("Failed to create srv: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ D3D11_SAMPLER_DESC sampDesc = {};
+ sampDesc.Filter = D3D11_FILTER_MIN_POINT_MAG_MIP_LINEAR;
+ sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
+ sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
+ sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
+ sampDesc.MaxAnisotropy = 1.0f;
+
+ hr = dev->CreateSamplerState(&sampDesc, &m_res.sampler);
+ if (FAILED(hr)) {
+ qWarning("Failed to create sampler state: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ m_res.valid = true;
+ return true;
+}
+
+void Window::releaseResources()
+{
+ RELEASE(m_res.vertexShader);
+ RELEASE(m_res.pixelShader);
+ RELEASE(m_res.texture);
+ RELEASE(m_res.resolveTexture);
+ RELEASE(m_res.textureSrv);
+ RELEASE(m_res.sampler);
+
+ m_res.valid = false;
+}
+
+void Window::render()
+{
+ if (!isExposed() || !m_swapchain.swapchain || !m_swapchain.tex || !m_swapchain.rtv)
+ return;
+
+ // if the window got resized, the swapchain buffers must be resized as well
+ if (m_swapchain.pixelSize != m_engine->swapchainSizeForWindow(this))
+ m_engine->resizeSwapchain(&m_swapchain, this);
+
+ if (!m_res.valid) {
+ if (!initResources())
+ return;
+ }
+
+ // get some content into m_res.texture from Qt Quick
+ updateQuick();
+
+ // now onto our own drawing, targeting the window
+ ID3D11DeviceContext1 *ctx = m_engine->context();
+ const QSize viewSize = m_swapchain.pixelSize;
+
+ const float clearColor[] = { 0.4f, 0.7f, 0.0f, 1.0f };
+ ctx->ClearRenderTargetView(m_swapchain.rtv, clearColor);
+ ctx->ClearDepthStencilView(m_swapchain.dsv, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
+
+ ctx->OMSetRenderTargets(1, &m_swapchain.rtv, m_swapchain.dsv);
+
+ const D3D11_VIEWPORT viewport = { 0.0f, 0.0f, float(viewSize.width()), float(viewSize.height()),
+ 0.f, 1.0f };
+ ctx->RSSetViewports(1, &viewport);
+
+ // draw a textured quad
+
+ ctx->VSSetShader(m_res.vertexShader, nullptr, 0);
+ ctx->PSSetShader(m_res.pixelShader, nullptr, 0);
+
+ ctx->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
+ ctx->IASetInputLayout(nullptr);
+ ctx->OMSetDepthStencilState(nullptr, 0);
+ ctx->OMSetBlendState(nullptr, nullptr, 0xffffffff);
+ ctx->RSSetState(nullptr);
+
+ ctx->PSSetShaderResources(0, 1, &m_res.textureSrv);
+ ctx->PSSetSamplers(0, 1, &m_res.sampler);
+
+ ctx->Draw(6, 0);
+
+ m_swapchain.swapchain->Present(1, 0);
+
+ requestUpdate(); // will lead to eventually getting a QEvent::UpdateRequest
+}
+
+void Window::updateQuick()
+{
+ if (!m_quickDirty)
+ return;
+
+ m_quickDirty = false;
+
+ if (!m_quickInitialized) {
+ // In addition to setSceneGraphBackend, we need a call to
+ // setGraphicsDevice to tell Qt Quick what ID3D11Device(Context) to use
+ // (i.e. we want it to use ours, not to create new ones).
+ m_quickWindow->setGraphicsDevice(QQuickGraphicsDevice::fromDeviceAndContext(m_engine->device(), m_engine->context()));
+
+ // Now we can kick off the scenegraph.
+ if (!m_renderControl->initialize())
+ qWarning("Failed to initialize redirected Qt Quick rendering");
+
+ // Redirect Qt Quick's output. (note that the QSGTexture::NativeTexture
+ // struct is expected to contain a pointer to the native object, even
+ // if the native object type is a pointer, such as ID3D11Texture2D*)
+ m_quickWindow->setRenderTarget(QQuickRenderTarget::fromNativeTexture({ &m_res.texture, 0 },
+ QSize(QML_WIDTH, QML_HEIGHT),
+ SAMPLE_COUNT));
+
+ m_quickInitialized = true;
+ }
+
+ m_renderControl->polishItems();
+
+ m_renderControl->beginFrame();
+ m_renderControl->sync();
+ m_renderControl->render();
+ m_renderControl->endFrame(); // Qt Quick's rendering commands are submitted to the device context here
+
+ if (SAMPLE_COUNT > 1)
+ m_engine->context()->ResolveSubresource(m_res.resolveTexture, 0, m_res.texture, 0, DXGI_FORMAT_R8G8B8A8_UNORM);
+}
diff --git a/examples/quick/rendercontrol/rendercontrol_d3d11/window.h b/examples/quick/rendercontrol/rendercontrol_d3d11/window.h
new file mode 100644
index 0000000000..6020be288e
--- /dev/null
+++ b/examples/quick/rendercontrol/rendercontrol_d3d11/window.h
@@ -0,0 +1,100 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 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 WINDOW_H
+#define WINDOW_H
+
+#include <QWindow>
+#include "engine.h"
+
+class QQuickRenderControl;
+class QQuickWindow;
+class QQmlEngine;
+class QQmlComponent;
+
+class Window : public QWindow
+{
+public:
+ Window(Engine *engine);
+ ~Window();
+
+protected:
+ void exposeEvent(QExposeEvent *e) override;
+ void mousePressEvent(QMouseEvent *e) override;
+ void mouseReleaseEvent(QMouseEvent *e) override;
+ bool event(QEvent *e) override;
+
+private:
+ void render();
+ bool initResources();
+ void releaseResources();
+ void updateQuick();
+
+ Engine *m_engine;
+ QQuickRenderControl *m_renderControl;
+ QQuickWindow *m_quickWindow;
+ QQmlEngine *m_qmlEngine;
+ QQmlComponent *m_qmlComponent;
+ bool m_quickInitialized = false;
+ bool m_quickDirty = true;
+
+ Swapchain m_swapchain = {};
+ struct {
+ bool valid = false;
+ ID3D11VertexShader *vertexShader = nullptr;
+ ID3D11PixelShader *pixelShader = nullptr;
+ ID3D11Texture2D *texture = nullptr;
+ ID3D11Texture2D *resolveTexture = nullptr;
+ ID3D11ShaderResourceView *textureSrv = nullptr;
+ ID3D11SamplerState *sampler = nullptr;
+ } m_res;
+};
+
+#endif
diff --git a/examples/quick/rendercontrol/rendercontrol_opengl/doc/images/rendercontrol-example.jpg b/examples/quick/rendercontrol/rendercontrol_opengl/doc/images/rendercontrol-opengl-example.jpg
index a899ebe7f5..a899ebe7f5 100644
--- a/examples/quick/rendercontrol/rendercontrol_opengl/doc/images/rendercontrol-example.jpg
+++ b/examples/quick/rendercontrol/rendercontrol_opengl/doc/images/rendercontrol-opengl-example.jpg
Binary files differ
diff --git a/examples/quick/rendercontrol/rendercontrol_opengl/doc/src/rendercontrol.qdoc b/examples/quick/rendercontrol/rendercontrol_opengl/doc/src/rendercontrol_opengl.qdoc
index 9b6b075a5b..a4a648e5de 100644
--- a/examples/quick/rendercontrol/rendercontrol_opengl/doc/src/rendercontrol.qdoc
+++ b/examples/quick/rendercontrol/rendercontrol_opengl/doc/src/rendercontrol_opengl.qdoc
@@ -26,8 +26,8 @@
****************************************************************************/
/*!
- \title QQuickRenderControl Example
+ \title QQuickRenderControl OpenGL Example
\example rendercontrol/rendercontrol_opengl
\brief Shows how to render a Qt Quick scene into a texture that is then used by a non-Quick based OpenGL renderer.
- \image rendercontrol-example.jpg
+ \image rendercontrol-opengl-example.jpg
*/
diff --git a/src/quick/CMakeLists.txt b/src/quick/CMakeLists.txt
index 88ddc4e257..466c9aedb0 100644
--- a/src/quick/CMakeLists.txt
+++ b/src/quick/CMakeLists.txt
@@ -37,6 +37,7 @@ qt_add_module(Quick
items/qquickflickable_p_p.h
items/qquickflickablebehavior_p.h
items/qquickfocusscope.cpp items/qquickfocusscope_p.h
+ items/qquickgraphicsdevice.cpp items/qquickgraphicsdevice.h items/qquickgraphicsdevice_p.h
items/qquickgraphicsinfo.cpp items/qquickgraphicsinfo_p.h
items/qquickimage.cpp items/qquickimage_p.h
items/qquickimage_p_p.h
@@ -64,6 +65,7 @@ qt_add_module(Quick
items/qquickrectangle.cpp items/qquickrectangle_p.h
items/qquickrectangle_p_p.h
items/qquickrendercontrol.cpp items/qquickrendercontrol.h items/qquickrendercontrol_p.h
+ items/qquickrendertarget.cpp items/qquickrendertarget.h items/qquickrendertarget_p.h
items/qquickscalegrid.cpp
items/qquickscalegrid_p_p.h
items/qquickscreen.cpp items/qquickscreen_p.h
diff --git a/src/quick/items/items.pri b/src/quick/items/items.pri
index c2078b0e04..2d232a0b0a 100644
--- a/src/quick/items/items.pri
+++ b/src/quick/items/items.pri
@@ -65,7 +65,11 @@ HEADERS += \
$$PWD/qquickpalette_p.h \
$$PWD/qquickcolorgroup_p.h \
$$PWD/qquickpalettecolorprovider_p.h \
- $$PWD/qquickpaletteproviderprivatebase_p.h
+ $$PWD/qquickpaletteproviderprivatebase_p.h \
+ $$PWD/qquickrendertarget.h \
+ $$PWD/qquickrendertarget_p.h \
+ $$PWD/qquickgraphicsdevice.h \
+ $$PWD/qquickgraphicsdevice_p.h
SOURCES += \
$$PWD/qquickevents.cpp \
@@ -108,7 +112,9 @@ SOURCES += \
$$PWD/qquickitemgrabresult.cpp \
$$PWD/qquickpalettecolorprovider.cpp \
$$PWD/qquickcolorgroup.cpp \
- $$PWD/qquickpalette.cpp
+ $$PWD/qquickpalette.cpp \
+ $$PWD/qquickrendertarget.cpp \
+ $$PWD/qquickgraphicsdevice.cpp
qtConfig(quick-draganddrop) {
HEADERS += \
diff --git a/src/quick/items/qquickgraphicsdevice.cpp b/src/quick/items/qquickgraphicsdevice.cpp
new file mode 100644
index 0000000000..029546a5f8
--- /dev/null
+++ b/src/quick/items/qquickgraphicsdevice.cpp
@@ -0,0 +1,216 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick 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 "qquickgraphicsdevice_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QQuickGraphicsDevice
+ \since 6.0
+ \inmodule QtQuick
+
+ \brief The QQuickGraphicsDevice class provides an opaque container for
+ native graphics objects representing graphics devices or contexts.
+
+ \sa QQuickWindow::setGraphicsDevice(), QQuickRenderTarget
+*/
+
+/*!
+ Constructs a default QQuickGraphicsDEvice that does not reference any native
+ objects.
+ */
+QQuickGraphicsDevice::QQuickGraphicsDevice()
+ : d(new QQuickGraphicsDevicePrivate)
+{
+}
+
+/*!
+ \internal
+ */
+void QQuickGraphicsDevice::detach()
+{
+ qAtomicDetach(d);
+}
+
+/*!
+ \internal
+ */
+QQuickGraphicsDevice::QQuickGraphicsDevice(const QQuickGraphicsDevice &other)
+ : d(other.d)
+{
+ d->ref.ref();
+}
+
+/*!
+ \internal
+ */
+QQuickGraphicsDevice &QQuickGraphicsDevice::operator=(const QQuickGraphicsDevice &other)
+{
+ qAtomicAssign(d, other.d);
+ return *this;
+}
+
+/*!
+ Destructor.
+ */
+QQuickGraphicsDevice::~QQuickGraphicsDevice()
+{
+ if (!d->ref.deref())
+ delete d;
+}
+
+/*!
+ Constructs a default QQuickRenderTarget that does not reference any native
+ objects.
+ */
+bool QQuickGraphicsDevice::isNull() const
+{
+ return d->type == QQuickGraphicsDevicePrivate::Type::Null;
+}
+
+/*!
+ \return a new QQuickGraphicsDevice referencing an existing QOpenGLContext.
+
+ This factory function is suitable for OpenGL.
+ */
+QQuickGraphicsDevice QQuickGraphicsDevice::fromOpenGLContext(QOpenGLContext *context)
+{
+ QQuickGraphicsDevice dev;
+ QQuickGraphicsDevicePrivate *d = QQuickGraphicsDevicePrivate::get(&dev);
+ d->type = QQuickGraphicsDevicePrivate::Type::OpenGLContext;
+ d->u.context = context;
+ return dev;
+}
+
+/*!
+ \return a new QQuickGraphicsDevice referencing a native device and context
+ object.
+
+ This factory function is suitable for:
+
+ \list
+
+ \li Direct3D11 - \a device is expected to be a \c{ID3D11Device*}, \a
+ context is expected to be a \c{ID3D11DeviceContext*}.
+
+ \endlist
+
+ \note the resulting QQuickGraphicsDevice does not own any native resources,
+ it merely contains references. It is the caller's responsibility to ensure
+ that the native resource exists as long as necessary.
+
+ */
+QQuickGraphicsDevice QQuickGraphicsDevice::fromDeviceAndContext(void *device, void *context)
+{
+ QQuickGraphicsDevice dev;
+ QQuickGraphicsDevicePrivate *d = QQuickGraphicsDevicePrivate::get(&dev);
+ d->type = QQuickGraphicsDevicePrivate::Type::DeviceAndContext;
+ d->u.deviceAndContext = { device, context };
+ return dev;
+}
+
+/*!
+ \return a new QQuickGraphicsDevice referencing a native device and command
+ queue object.
+
+ This factory function is suitable for:
+
+ \list
+
+ \li Metal - \a device is expected to be a \c{MTLDevice*}, \a cmdQueue is
+ expected to be a \c{MTLCommandQueue*}.
+
+ \endlist
+
+ \note the resulting QQuickGraphicsDevice does not own any native resources,
+ it merely contains references. It is the caller's responsibility to ensure
+ that the native resource exists as long as necessary.
+
+ */
+QQuickGraphicsDevice QQuickGraphicsDevice::fromDeviceAndCommandQueue(void *device, void *cmdQueue)
+{
+ QQuickGraphicsDevice dev;
+ QQuickGraphicsDevicePrivate *d = QQuickGraphicsDevicePrivate::get(&dev);
+ d->type = QQuickGraphicsDevicePrivate::Type::DeviceAndCommandQueue;
+ d->u.deviceAndCommandQueue = { device, cmdQueue };
+ return dev;
+}
+
+/*!
+ \return a new QQuickGraphicsDevice referencing a native device and related
+ objects.
+
+ This factory function is suitable for:
+
+ \list
+
+ \li Vulkan - \a physicalDevice is expected to be \c VkPhysicalDevice, \a
+ device is expected to be a \a VkDevice, while \a queueFamilyIndex is the
+ index of the graphics queue family on the device.
+
+ \endlist
+
+ \note the resulting QQuickGraphicsDevice does not own any native resources,
+ it merely contains references. It is the caller's responsibility to ensure
+ that the native resource exists as long as necessary.
+
+ */
+QQuickGraphicsDevice QQuickGraphicsDevice::fromDeviceObjects(void *physicalDevice, void *device, int queueFamilyIndex)
+{
+ QQuickGraphicsDevice dev;
+ QQuickGraphicsDevicePrivate *d = QQuickGraphicsDevicePrivate::get(&dev);
+ d->type = QQuickGraphicsDevicePrivate::Type::DeviceObjects;
+ d->u.deviceObjects = { physicalDevice, device, queueFamilyIndex };
+ return dev;
+}
+
+QQuickGraphicsDevicePrivate::QQuickGraphicsDevicePrivate()
+ : ref(1)
+{
+}
+
+QQuickGraphicsDevicePrivate::QQuickGraphicsDevicePrivate(const QQuickGraphicsDevicePrivate *other)
+ : ref(1),
+ type(other->type),
+ u(other->u)
+{
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/items/qquickgraphicsdevice.h b/src/quick/items/qquickgraphicsdevice.h
new file mode 100644
index 0000000000..811d510a5f
--- /dev/null
+++ b/src/quick/items/qquickgraphicsdevice.h
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick 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 QQUICKGRAPHICSDEVICE_H
+#define QQUICKGRAPHICSDEVICE_H
+
+#include <QtQuick/qtquickglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQuickGraphicsDevicePrivate;
+class QOpenGLContext;
+
+class Q_QUICK_EXPORT QQuickGraphicsDevice
+{
+public:
+ QQuickGraphicsDevice();
+ ~QQuickGraphicsDevice();
+ QQuickGraphicsDevice(const QQuickGraphicsDevice &other);
+ QQuickGraphicsDevice &operator=(const QQuickGraphicsDevice &other);
+
+ bool isNull() const;
+
+ static QQuickGraphicsDevice fromOpenGLContext(QOpenGLContext *context);
+ static QQuickGraphicsDevice fromDeviceAndContext(void *device, void *context);
+ static QQuickGraphicsDevice fromDeviceAndCommandQueue(void *device, void *cmdQueue);
+ static QQuickGraphicsDevice fromDeviceObjects(void *physicalDevice, void *device, int queueFamilyIndex);
+
+private:
+ void detach();
+ QQuickGraphicsDevicePrivate *d;
+ friend class QQuickGraphicsDevicePrivate;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKGRAPHICSDEVICE_H
diff --git a/src/quick/items/qquickgraphicsdevice_p.h b/src/quick/items/qquickgraphicsdevice_p.h
new file mode 100644
index 0000000000..0a206725a6
--- /dev/null
+++ b/src/quick/items/qquickgraphicsdevice_p.h
@@ -0,0 +1,105 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick 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 QQUICKGRAPHICSDEVICE_P_H
+#define QQUICKGRAPHICSDEVICE_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 <QtQuick/private/qtquickglobal_p.h>
+#include <QAtomicInt>
+#include "qquickgraphicsdevice.h"
+
+QT_BEGIN_NAMESPACE
+
+class Q_QUICK_PRIVATE_EXPORT QQuickGraphicsDevicePrivate
+{
+public:
+ static QQuickGraphicsDevicePrivate *get(QQuickGraphicsDevice *p) { return p->d; }
+ static const QQuickGraphicsDevicePrivate *get(const QQuickGraphicsDevice *p) { return p->d; }
+ QQuickGraphicsDevicePrivate();
+ QQuickGraphicsDevicePrivate(const QQuickGraphicsDevicePrivate *other);
+
+ enum class Type {
+ Null,
+ OpenGLContext,
+ DeviceAndContext,
+ DeviceAndCommandQueue,
+ DeviceObjects
+ };
+
+ QAtomicInt ref;
+ Type type = Type::Null;
+
+ struct DeviceAndContext {
+ void *device;
+ void *context;
+ };
+
+ struct DeviceAndCommandQueue {
+ void *device;
+ void *cmdQueue;
+ };
+
+ struct DeviceObjects {
+ void *physicalDevice;
+ void *device;
+ int queueFamilyIndex;
+ };
+
+ union {
+ QOpenGLContext *context;
+ DeviceAndContext deviceAndContext;
+ DeviceAndCommandQueue deviceAndCommandQueue;
+ DeviceObjects deviceObjects;
+ } u;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKGRAPHICSDEVICE_P_H
diff --git a/src/quick/items/qquickrendercontrol.cpp b/src/quick/items/qquickrendercontrol.cpp
index 190e0942ab..b14a3f35f7 100644
--- a/src/quick/items/qquickrendercontrol.cpp
+++ b/src/quick/items/qquickrendercontrol.cpp
@@ -43,29 +43,36 @@
#include <QtCore/QCoreApplication>
#include <QtCore/QTime>
#include <QtQuick/private/qquickanimatorcontroller_p.h>
+#include <QtQuick/private/qsgdefaultrendercontext_p.h>
+#include <QtQuick/private/qsgrhisupport_p.h>
#if QT_CONFIG(opengl)
# include <QOpenGLContext>
-# include <QtQuick/private/qsgdefaultrendercontext_p.h>
#if QT_CONFIG(quick_shadereffect)
# include <QtQuick/private/qquickopenglshadereffectnode_p.h>
#endif
#endif
#include <QtGui/private/qguiapplication_p.h>
#include <qpa/qplatformintegration.h>
+#include <QtGui/qoffscreensurface.h>
#include <QtQml/private/qqmlglobal_p.h>
#include <QtQuick/QQuickWindow>
+#include <QtQuick/QQuickRenderTarget>
#include <QtQuick/private/qquickwindow_p.h>
#include <QtQuick/private/qquickitem_p.h>
#include <QtQuick/private/qsgsoftwarerenderer_p.h>
#include <QtCore/private/qobject_p.h>
+#include <QtQuick/private/qquickwindow_p.h>
+#include <QtGui/private/qrhi_p.h>
+
QT_BEGIN_NAMESPACE
#if QT_CONFIG(opengl)
extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha);
#endif
+
/*!
\class QQuickRenderControl
@@ -136,9 +143,14 @@ extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_
QSGContext *QQuickRenderControlPrivate::sg = nullptr;
-QQuickRenderControlPrivate::QQuickRenderControlPrivate()
- : initialized(0),
- window(nullptr)
+QQuickRenderControlPrivate::QQuickRenderControlPrivate(QQuickRenderControl *renderControl)
+ : q(renderControl),
+ initialized(false),
+ window(nullptr),
+ rhi(nullptr),
+ cb(nullptr),
+ offscreenSurface(nullptr),
+ sampleCount(1)
{
if (!sg) {
qAddPostRoutine(cleanup);
@@ -158,7 +170,7 @@ void QQuickRenderControlPrivate::cleanup()
object \a parent.
*/
QQuickRenderControl::QQuickRenderControl(QObject *parent)
- : QObject(*(new QQuickRenderControlPrivate), parent)
+ : QObject(*(new QQuickRenderControlPrivate(this)), parent)
{
}
@@ -182,11 +194,16 @@ QQuickRenderControl::~QQuickRenderControl()
d->windowDestroyed();
delete d->rc;
+
+ d->resetRhi();
}
void QQuickRenderControlPrivate::windowDestroyed()
{
if (window) {
+ QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
+ cd->cleanupNodesOnShutdown();
+
rc->invalidate();
QQuickWindowPrivate::get(window)->animationController.reset();
@@ -215,6 +232,90 @@ void QQuickRenderControl::prepareThread(QThread *targetThread)
}
/*!
+ Sets the number of samples to use for multisampling. When \a sampleCount is
+ 0 or 1, multisampling is disabled.
+
+ \note This function is always used in combination with a multisample render
+ target, which means \a sampleCount must match the sample count passed to
+ QQuickRenderTarget::fromNativeTexture(), which in turn must match the
+ sample count of the native texture.
+
+ \since 6.0
+
+ \sa initialize(), QQuickRenderTarget
+ */
+void QQuickRenderControl::setSamples(int sampleCount)
+{
+ Q_D(QQuickRenderControl);
+ d->sampleCount = qMax(1, sampleCount);
+}
+
+/*!
+ \return the current sample count. 1 or 0 means no multisampling.
+
+ \since 6.0
+ */
+int QQuickRenderControl::samples() const
+{
+ Q_D(const QQuickRenderControl);
+ return d->sampleCount;
+}
+
+/*!
+ Initializes the scene graph resources. When using a graphics API, such as
+ Vulkan, Metal, OpenGL, or Direct3D, for Qt Quick rendering,
+ QQuickRenderControl will set up an appropriate rendering engine when this
+ function is called. This rendering infrastructure exists as long as the
+ QQuickRenderControl exists.
+
+ To control what graphics API Qt Quick uses, call
+ QQuickWindow::setSceneGraphBackend() with one of the
+ QSGRendererInterface:GraphicsApi constants. That must be done before
+ calling this function.
+
+ To prevent the scenegraph from creating its own device and context objects,
+ specify an appropriate QQuickGraphicsDevice, wrapping existing graphics
+ objects, by calling QQuickWindow::setGraphicsDevice().
+
+ \note This function does not need to be, and must not be, called when using
+ the \c software adaptation of Qt Quick.
+
+ \since 6.0
+
+ \sa QQuickRenderTarget, QQuickGraphicsDevice
+ */
+bool QQuickRenderControl::initialize()
+{
+ Q_D(QQuickRenderControl);
+
+ if (!d->window) {
+ qWarning("QQuickRenderControl::initialize called with no associated window");
+ return false;
+ }
+
+ if (!d->initRhi())
+ return false;
+
+ QQuickWindowPrivate *wd = QQuickWindowPrivate::get(d->window);
+ wd->rhi = d->rhi;
+
+ QSGDefaultRenderContext *renderContext = qobject_cast<QSGDefaultRenderContext *>(d->rc);
+ if (renderContext) {
+ QSGDefaultRenderContext::InitParams params;
+ params.rhi = d->rhi;
+ params.sampleCount = d->sampleCount;
+ params.initialSurfacePixelSize = d->window->size() * d->window->effectiveDevicePixelRatio();
+ params.maybeSurface = d->window;
+ renderContext->initialize(&params);
+ } else {
+ qWarning("QRhi is only compatible with default adaptation");
+ return false;
+ }
+
+ return true;
+}
+
+/*!
Initializes the scene graph resources. The context \a gl has to be the
current OpenGL context or null if it is not relevant because a Qt Quick
backend other than OpenGL is in use.
@@ -295,24 +396,33 @@ bool QQuickRenderControl::sync()
return false;
QQuickWindowPrivate *cd = QQuickWindowPrivate::get(d->window);
+ // we may not have a d->rhi (software backend) hence the check is important
+ if (d->rhi) {
+ if (!d->rhi->isRecordingFrame()) {
+ qWarning("QQuickRenderControl can only sync when beginFrame() has been called");
+ return false;
+ }
+ if (!d->cb) {
+ qWarning("QQuickRenderControl cannot be used with QRhi when no QRhiCommandBuffer is provided");
+ return false;
+ }
+ cd->setCustomCommandBuffer(d->cb);
+ }
+
cd->syncSceneGraph();
d->rc->endSync();
- // TODO: find out if the sync actually caused a scenegraph update.
return true;
}
/*!
- Stop rendering and release resources. Requires a current context.
+ Stop rendering and release resources.
This is the equivalent of the cleanup operations that happen with a
real QQuickWindow when the window becomes hidden.
This function is called from the destructor. Therefore there will
- typically be no need to call it directly. Pay attention however to
- the fact that this requires the context, that was passed to
- initialize(), to be the current one at the time of destroying the
- QQuickRenderControl instance.
+ typically be no need to call it directly.
Once invalidate() has been called, it is possible to reuse the
QQuickRenderControl instance by calling initialize() again.
@@ -353,6 +463,19 @@ void QQuickRenderControl::render()
return;
QQuickWindowPrivate *cd = QQuickWindowPrivate::get(d->window);
+ // we may not have a d->rhi (software backend) hence the check is important
+ if (d->rhi) {
+ if (!d->rhi->isRecordingFrame()) {
+ qWarning("QQuickRenderControl can only render when beginFrame() has been called");
+ return;
+ }
+ if (!d->cb) {
+ qWarning("QQuickRenderControl cannot be used with QRhi when no QRhiCommandBuffer is provided");
+ return;
+ }
+ cd->setCustomCommandBuffer(d->cb);
+ }
+
cd->renderSceneGraph(d->window->size());
}
@@ -378,48 +501,50 @@ void QQuickRenderControl::render()
for example. This will lead to better performance.
*/
-/*!
- Grabs the contents of the scene and returns it as an image.
-
- \note Requires the context to be current.
- */
-QImage QQuickRenderControl::grab()
+QImage QQuickRenderControlPrivate::grab()
{
- Q_D(QQuickRenderControl);
- if (!d->window)
+ if (!window)
return QImage();
QImage grabContent;
- if (d->window->rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL) {
+ if (rhi) {
+
+ // As documented by QQuickWindow::grabWindow(): Nothing to do here, we
+ // do not support "grabbing" with an application-provided render target
+ // in Qt 6. (with the exception of the software backend because that
+ // does not support custom render targets, so the grab implementation
+ // here is still valuable)
+
+ } else if (window->rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL) {
#if QT_CONFIG(opengl)
- QQuickWindowPrivate *cd = QQuickWindowPrivate::get(d->window);
+ QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
cd->polishItems();
cd->syncSceneGraph();
- d->rc->endSync();
- render();
- const bool alpha = d->window->format().alphaBufferSize() > 0 && d->window->color().alpha() < 255;
- grabContent = qt_gl_read_framebuffer(d->window->size() * d->window->effectiveDevicePixelRatio(), alpha, alpha);
- if (QQuickRenderControl::renderWindowFor(d->window)) {
- grabContent.setDevicePixelRatio(d->window->effectiveDevicePixelRatio());
+ rc->endSync();
+ q->render();
+ const bool alpha = window->format().alphaBufferSize() > 0 && window->color().alpha() < 255;
+ grabContent = qt_gl_read_framebuffer(window->size() * window->effectiveDevicePixelRatio(), alpha, alpha);
+ if (QQuickRenderControl::renderWindowFor(window)) {
+ grabContent.setDevicePixelRatio(window->effectiveDevicePixelRatio());
}
#endif
#if QT_CONFIG(thread)
- } else if (d->window->rendererInterface()->graphicsApi() == QSGRendererInterface::Software) {
- QQuickWindowPrivate *cd = QQuickWindowPrivate::get(d->window);
+ } else if (window->rendererInterface()->graphicsApi() == QSGRendererInterface::Software) {
+ QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
cd->polishItems();
cd->syncSceneGraph();
QSGSoftwareRenderer *softwareRenderer = static_cast<QSGSoftwareRenderer *>(cd->renderer);
if (softwareRenderer) {
- const qreal dpr = d->window->effectiveDevicePixelRatio();
- const QSize imageSize = d->window->size() * dpr;
+ const qreal dpr = window->effectiveDevicePixelRatio();
+ const QSize imageSize = window->size() * dpr;
grabContent = QImage(imageSize, QImage::Format_ARGB32_Premultiplied);
grabContent.setDevicePixelRatio(dpr);
QPaintDevice *prevDev = softwareRenderer->currentPaintDevice();
softwareRenderer->setCurrentPaintDevice(&grabContent);
softwareRenderer->markDirty();
- d->rc->endSync();
- render();
+ rc->endSync();
+ q->render();
softwareRenderer->setCurrentPaintDevice(prevDev);
}
#endif
@@ -474,6 +599,131 @@ QWindow *QQuickRenderControl::renderWindowFor(QQuickWindow *win, QPoint *offset)
return nullptr;
}
+/*!
+ \return the QQuickWindow this QQuickRenderControl is associated with.
+
+ \note A QQuickRenderControl gets associated with a QQuickWindow when
+ constructing the QQuickWindow. The return value from this function is null
+ before that point.
+
+ \since 6.0
+ */
+QQuickWindow *QQuickRenderControl::window() const
+{
+ Q_D(const QQuickRenderControl);
+ return d->window;
+}
+
+/*!
+ Specifies the start of a graphics frame. Calls to sync() or render() must
+ be enclosed by calls to beginFrame() and endFrame().
+
+ Unlike the earlier OpenGL-only world of Qt 5, rendering with other graphics
+ APIs requires more well-defined points of starting and ending a frame. When
+ manually driving the rendering loop via QQuickRenderControl, it now falls
+ to the user of QQuickRenderControl to specify these points.
+
+ A typical update step, including initialization of rendering into an
+ existing texture, could like like the following. The example snippet
+ assumes Direct3D 11 but the same concepts apply other graphics APIs as
+ well.
+
+ \badcode
+ if (!m_quickInitialized) {
+ m_quickWindow->setGraphicsDevice(QQuickGraphicsDevice::fromDeviceAndContext(m_engine->device(), m_engine->context()));
+
+ if (!m_renderControl->initialize())
+ qWarning("Failed to initialize redirected Qt Quick rendering");
+
+ m_quickWindow->setRenderTarget(QQuickRenderTarget::fromNativeTexture({ &m_res.texture, 0 },
+ QSize(QML_WIDTH, QML_HEIGHT),
+ SAMPLE_COUNT));
+
+ m_quickInitialized = true;
+ }
+
+ m_renderControl->polishItems();
+
+ m_renderControl->beginFrame();
+ m_renderControl->sync();
+ m_renderControl->render();
+ m_renderControl->endFrame(); // Qt Quick's rendering commands are submitted to the device context here
+ \endcode
+
+ \since 6.0
+
+ \sa endFrame(), initialize(), sync(), render(), QQuickGraphicsDevice, QQuickRenderTarget
+ */
+void QQuickRenderControl::beginFrame()
+{
+ Q_D(QQuickRenderControl);
+ if (!d->rhi || d->rhi->isRecordingFrame())
+ return;
+
+ d->rhi->beginOffscreenFrame(&d->cb, QRhi::ExternalContentsInPass); // must specify ExternalContents since we do not know in advance
+}
+
+/*!
+ Specifies the end of a graphics frame. Calls to sync() or render() must be
+ enclosed by calls to beginFrame() and endFrame().
+
+ When this function is called, any graphics commands enqueued by the
+ scenegraph are submitted to the context or command queue, whichever is
+ applicable.
+
+ \since 6.0
+
+ \sa beginFrame(), initialize(), sync(), render(), QQuickGraphicsDevice, QQuickRenderTarget
+ */
+void QQuickRenderControl::endFrame()
+{
+ Q_D(QQuickRenderControl);
+ if (!d->rhi || !d->rhi->isRecordingFrame())
+ return;
+
+ d->rhi->endOffscreenFrame();
+ d->cb = nullptr;
+}
+
+bool QQuickRenderControlPrivate::initRhi()
+{
+ // initialize() - invalidate() - initialize() uses the QRhi the first
+ // initialize() created, so if already exists, we are done
+ if (rhi)
+ return true;
+
+ QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
+
+ // sanity check for Vulkan
+#if QT_CONFIG(vulkan)
+ if (rhiSupport->rhiBackend() == QRhi::Vulkan && !window->vulkanInstance()) {
+ qWarning("QQuickRenderControl: No QVulkanInstance set for QQuickWindow, cannot initialize");
+ return false;
+ }
+#endif
+
+ // for OpenGL
+ if (!offscreenSurface)
+ offscreenSurface = rhiSupport->maybeCreateOffscreenSurface(window);
+
+ rhi = rhiSupport->createRhi(window, offscreenSurface);
+ if (!rhi) {
+ qWarning("QQuickRenderControl: Failed to initialize QRhi");
+ return false;
+ }
+
+ return true;
+}
+
+void QQuickRenderControlPrivate::resetRhi()
+{
+ delete rhi;
+ rhi = nullptr;
+
+ delete offscreenSurface;
+ offscreenSurface = nullptr;
+}
+
QT_END_NAMESPACE
#include "moc_qquickrendercontrol.cpp"
diff --git a/src/quick/items/qquickrendercontrol.h b/src/quick/items/qquickrendercontrol.h
index 8ec9b8aafc..cdcad589f6 100644
--- a/src/quick/items/qquickrendercontrol.h
+++ b/src/quick/items/qquickrendercontrol.h
@@ -41,7 +41,7 @@
#define QQUICKRENDERCONTROL_H
#include <QtQuick/qtquickglobal.h>
-#include <QtGui/QImage>
+#include <QtGui/qimage.h>
QT_BEGIN_NAMESPACE
@@ -59,18 +59,27 @@ public:
~QQuickRenderControl() override;
void prepareThread(QThread *targetThread);
- void initialize(QOpenGLContext *gl);
+
+ void setSamples(int sampleCount);
+ int samples() const;
+
+ bool initialize();
+ void initialize(QOpenGLContext *gl); // ### Qt 6 remove
+
void invalidate();
+ void beginFrame();
+ void endFrame();
+
void polishItems();
- void render();
bool sync();
-
- QImage grab();
+ void render();
static QWindow *renderWindowFor(QQuickWindow *win, QPoint *offset = nullptr);
virtual QWindow *renderWindow(QPoint *offset) { Q_UNUSED(offset); return nullptr; }
+ QQuickWindow *window() const;
+
Q_SIGNALS:
void renderRequested();
void sceneChanged();
diff --git a/src/quick/items/qquickrendercontrol_p.h b/src/quick/items/qquickrendercontrol_p.h
index 487af45db5..c24ea9b8c6 100644
--- a/src/quick/items/qquickrendercontrol_p.h
+++ b/src/quick/items/qquickrendercontrol_p.h
@@ -56,12 +56,16 @@
QT_BEGIN_NAMESPACE
-class QQuickRenderControlPrivate : public QObjectPrivate
+class QRhi;
+class QRhiCommandBuffer;
+class QOffscreenSurface;
+
+class Q_AUTOTEST_EXPORT QQuickRenderControlPrivate : public QObjectPrivate
{
public:
Q_DECLARE_PUBLIC(QQuickRenderControl)
- QQuickRenderControlPrivate();
+ QQuickRenderControlPrivate(QQuickRenderControl *renderControl);
static QQuickRenderControlPrivate *get(QQuickRenderControl *renderControl) {
return renderControl->d_func();
@@ -74,10 +78,20 @@ public:
void update();
void maybeUpdate();
+ bool initRhi();
+ void resetRhi();
+
+ QImage grab();
+
+ QQuickRenderControl *q;
bool initialized;
QQuickWindow *window;
static QSGContext *sg;
QSGRenderContext *rc;
+ QRhi *rhi;
+ QRhiCommandBuffer *cb;
+ QOffscreenSurface *offscreenSurface;
+ int sampleCount;
};
QT_END_NAMESPACE
diff --git a/src/quick/items/qquickrendertarget.cpp b/src/quick/items/qquickrendertarget.cpp
new file mode 100644
index 0000000000..924824e456
--- /dev/null
+++ b/src/quick/items/qquickrendertarget.cpp
@@ -0,0 +1,293 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick 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 "qquickrendertarget_p.h"
+#include <QtGui/private/qrhi_p.h>
+#include <QtQuick/private/qquickitem_p.h>
+#include <QtQuick/private/qquickwindow_p.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QQuickRenderTarget
+ \since 6.0
+ \inmodule QtQuick
+
+ \brief The QQuickRenderTarget class provides an opaque container for native
+ graphics resources specifying a render target, and associated metadata.
+
+ \sa QQuickWindow::setRenderTarget(), QQuickGraphicsDevice
+*/
+
+QQuickRenderTargetPrivate::QQuickRenderTargetPrivate()
+ : ref(1)
+{
+}
+
+QQuickRenderTargetPrivate::QQuickRenderTargetPrivate(const QQuickRenderTargetPrivate *other)
+ : ref(1),
+ type(other->type),
+ pixelSize(other->pixelSize),
+ sampleCount(other->sampleCount),
+ u(other->u)
+{
+}
+
+/*!
+ Constructs a default QQuickRenderTarget that does not reference any native
+ objects.
+ */
+QQuickRenderTarget::QQuickRenderTarget()
+ : d(new QQuickRenderTargetPrivate)
+{
+}
+
+/*!
+ \internal
+ */
+void QQuickRenderTarget::detach()
+{
+ qAtomicDetach(d);
+}
+
+/*!
+ \internal
+ */
+QQuickRenderTarget::QQuickRenderTarget(const QQuickRenderTarget &other)
+ : d(other.d)
+{
+ d->ref.ref();
+}
+
+/*!
+ \internal
+ */
+QQuickRenderTarget &QQuickRenderTarget::operator=(const QQuickRenderTarget &other)
+{
+ qAtomicAssign(d, other.d);
+ return *this;
+}
+
+/*!
+ Destructor.
+ */
+QQuickRenderTarget::~QQuickRenderTarget()
+{
+ if (!d->ref.deref())
+ delete d;
+}
+
+/*!
+ \return true if this QQuickRenderTarget is default constructed, referencing
+ no native objects.
+ */
+bool QQuickRenderTarget::isNull() const
+{
+ return d->type == QQuickRenderTargetPrivate::Type::Null;
+}
+
+/*!
+ \return a new QQuickRenderTarget referencing a native texture or image
+ object.
+
+ \a nativeTexture references a native resource, for example, a \c VkImage,
+ \c ID3D11Texture2D*, c MTLTexture*, or \c GLuint. Where applicable, this
+ must be complemented by the current layout of the image.
+
+ \note the \c object field in \a nativeTexture must always be a pointer to
+ the native object, even if the type is already a pointer.
+
+ \a pixelSize specifies the size of the image, in pixels. Currently only 2D
+ textures are supported.
+
+ \a sampleCount specific the number of samples. 0 or 1 means no
+ multisampling, while a value like 4 or 8 states that the native object is a
+ multisample texture.
+
+ \note the resulting QQuickRenderTarget does not own any native resources,
+ it merely contains references and the associated metadata of the size and
+ sample count. It is the caller's responsibility to ensure that the native
+ resource exists as long as necessary.
+
+ \sa QQuickWindow::setRenderTarget(), QQuickRenderControl
+ */
+QQuickRenderTarget QQuickRenderTarget::fromNativeTexture(const QSGTexture::NativeTexture &nativeTexture,
+ const QSize &pixelSize,
+ int sampleCount)
+{
+ QQuickRenderTarget rt;
+ QQuickRenderTargetPrivate *d = QQuickRenderTargetPrivate::get(&rt);
+
+ if (!nativeTexture.object) {
+ qWarning("QQuickRenderTarget: nativeTexture.object is null");
+ return rt;
+ }
+
+ if (pixelSize.isEmpty()) {
+ qWarning("QQuickRenderTarget: Cannot create with empty size");
+ return rt;
+ }
+
+ d->type = QQuickRenderTargetPrivate::Type::NativeTexture;
+ d->pixelSize = pixelSize;
+ d->sampleCount = qMax(1, sampleCount);
+ d->u.nativeTexture = nativeTexture;
+
+ return rt;
+}
+
+/*!
+ \internal
+ */
+QQuickRenderTarget QQuickRenderTarget::fromRhiRenderTarget(QRhiRenderTarget *renderTarget)
+{
+ QQuickRenderTarget rt;
+ QQuickRenderTargetPrivate *d = QQuickRenderTargetPrivate::get(&rt);
+
+ if (!renderTarget) {
+ qWarning("QQuickRenderTarget: Needs a valid QRhiRenderTarget");
+ return rt;
+ }
+
+ d->type = QQuickRenderTargetPrivate::Type::RhiRenderTarget;
+ d->pixelSize = renderTarget->pixelSize();
+ d->sampleCount = renderTarget->sampleCount();
+ d->u.rhiRt = renderTarget;
+
+ return rt;
+}
+
+/*!
+ \return true if \a a and \a b refer to the same set of native objects and
+ have matching associated data (size, sample count).
+ */
+bool operator==(const QQuickRenderTarget &a, const QQuickRenderTarget &b) Q_DECL_NOTHROW
+{
+ const QQuickRenderTargetPrivate *da = QQuickRenderTargetPrivate::get(&a);
+ const QQuickRenderTargetPrivate *db = QQuickRenderTargetPrivate::get(&b);
+ if (da->type != db->type
+ || da->pixelSize != db->pixelSize
+ || da->sampleCount != db->sampleCount)
+ {
+ return false;
+ }
+
+ switch (da->type) {
+ case QQuickRenderTargetPrivate::Type::Null:
+ break;
+ case QQuickRenderTargetPrivate::Type::NativeTexture:
+ if (da->u.nativeTexture.object != db->u.nativeTexture.object
+ || da->u.nativeTexture.layout != db->u.nativeTexture.layout)
+ return false;
+ break;
+ case QQuickRenderTargetPrivate::Type::RhiRenderTarget:
+ if (da->u.rhiRt != db->u.rhiRt)
+ return false;
+ break;
+ default:
+ break;
+ }
+
+ return true;
+}
+
+/*!
+ \return true if \a a and \a b refer to a different set of native objects,
+ or the associated data (size, sample count) does not match.
+ */
+bool operator!=(const QQuickRenderTarget &a, const QQuickRenderTarget &b) Q_DECL_NOTHROW
+{
+ return !(a == b);
+}
+
+bool QQuickRenderTargetPrivate::resolve(QRhi *rhi, QQuickWindowRenderTarget *dst)
+{
+ switch (type) {
+ case Type::Null:
+ dst->renderTarget = nullptr;
+ dst->owns = false;
+ return true;
+
+ case Type::NativeTexture:
+ {
+ QRhiTexture *texture = rhi->newTexture(QRhiTexture::RGBA8, pixelSize, sampleCount, QRhiTexture::RenderTarget);
+ if (!texture->buildFrom({ u.nativeTexture.object, u.nativeTexture.layout })) {
+ qWarning("Failed to build texture for QQuickRenderTarget");
+ return false;
+ }
+ QRhiRenderBuffer *depthStencil = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, pixelSize, sampleCount);
+ if (!depthStencil->build()) {
+ qWarning("Failed to build depth-stencil buffer for QQuickRenderTarget");
+ delete texture;
+ return false;
+ }
+
+ QRhiColorAttachment att(texture);
+ QRhiTextureRenderTargetDescription rtDesc(att);
+ rtDesc.setDepthStencilBuffer(depthStencil);
+ QRhiTextureRenderTarget *rt = rhi->newTextureRenderTarget(rtDesc);
+ QRhiRenderPassDescriptor *rp = rt->newCompatibleRenderPassDescriptor();
+ rt->setRenderPassDescriptor(rp);
+ if (!rt->build()) {
+ qWarning("Failed to build texture render target for QQuickRenderTarget");
+ delete rp;
+ delete depthStencil;
+ delete texture;
+ return false;
+ }
+ dst->renderTarget = rt;
+ dst->rpDesc = rp;
+ dst->texture = texture;
+ dst->depthStencil = depthStencil;
+ dst->owns = true; // ownership of the native resource itself is not transferred but the QRhi objects are on us now
+ }
+ return true;
+
+ case Type::RhiRenderTarget:
+ dst->renderTarget = u.rhiRt;
+ dst->owns = false;
+ return true;
+
+ default:
+ break;
+ }
+ return false;
+}
+
+QT_END_NAMESPACE
diff --git a/src/quick/items/qquickrendertarget.h b/src/quick/items/qquickrendertarget.h
new file mode 100644
index 0000000000..5139046ad4
--- /dev/null
+++ b/src/quick/items/qquickrendertarget.h
@@ -0,0 +1,78 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick 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 QQUICKRENDERTARGET_H
+#define QQUICKRENDERTARGET_H
+
+#include <QtQuick/qtquickglobal.h>
+#include <QtQuick/qsgtexture.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQuickRenderTargetPrivate;
+class QRhiRenderTarget;
+
+class Q_QUICK_EXPORT QQuickRenderTarget
+{
+public:
+ QQuickRenderTarget();
+ ~QQuickRenderTarget();
+ QQuickRenderTarget(const QQuickRenderTarget &other);
+ QQuickRenderTarget &operator=(const QQuickRenderTarget &other);
+
+ bool isNull() const;
+
+ static QQuickRenderTarget fromNativeTexture(const QSGTexture::NativeTexture &nativeTexture,
+ const QSize &pixelSize,
+ int sampleCount = 1);
+
+ static QQuickRenderTarget fromRhiRenderTarget(QRhiRenderTarget *renderTarget);
+
+private:
+ void detach();
+ QQuickRenderTargetPrivate *d;
+ friend class QQuickRenderTargetPrivate;
+};
+
+Q_QUICK_EXPORT bool operator==(const QQuickRenderTarget &a, const QQuickRenderTarget &b) Q_DECL_NOTHROW;
+Q_QUICK_EXPORT bool operator!=(const QQuickRenderTarget &a, const QQuickRenderTarget &b) Q_DECL_NOTHROW;
+
+QT_END_NAMESPACE
+
+#endif // QQUICKRENDERTARGET_H
diff --git a/src/quick/items/qquickrendertarget_p.h b/src/quick/items/qquickrendertarget_p.h
new file mode 100644
index 0000000000..628e5c277b
--- /dev/null
+++ b/src/quick/items/qquickrendertarget_p.h
@@ -0,0 +1,90 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick 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 QQUICKRENDERTARGET_P_H
+#define QQUICKRENDERTARGET_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 <QtQuick/private/qtquickglobal_p.h>
+#include "qquickrendertarget.h"
+#include <QAtomicInt>
+
+QT_BEGIN_NAMESPACE
+
+class QRhi;
+class QQuickWindowRenderTarget;
+
+class Q_QUICK_PRIVATE_EXPORT QQuickRenderTargetPrivate
+{
+public:
+ static QQuickRenderTargetPrivate *get(QQuickRenderTarget *rt) { return rt->d; }
+ static const QQuickRenderTargetPrivate *get(const QQuickRenderTarget *rt) { return rt->d; }
+ QQuickRenderTargetPrivate();
+ QQuickRenderTargetPrivate(const QQuickRenderTargetPrivate *other);
+ bool resolve(QRhi *rhi, QQuickWindowRenderTarget *dst);
+
+ enum class Type {
+ Null,
+ NativeTexture,
+ RhiRenderTarget
+ };
+
+ QAtomicInt ref;
+ Type type = Type::Null;
+ QSize pixelSize;
+ int sampleCount = 1;
+ union {
+ QSGTexture::NativeTexture nativeTexture;
+ QRhiRenderTarget *rhiRt;
+ } u;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKRENDERTARGET_P_H
diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp
index 4874af44ed..bb3aff0cd6 100644
--- a/src/quick/items/qquickwindow.cpp
+++ b/src/quick/items/qquickwindow.cpp
@@ -43,6 +43,7 @@
#include "qquickitem.h"
#include "qquickitem_p.h"
#include "qquickevents_p_p.h"
+#include "qquickgraphicsdevice_p.h"
#if QT_CONFIG(quick_draganddrop)
#include <private/qquickdrag_p.h>
@@ -424,16 +425,66 @@ void forceUpdate(QQuickItem *item)
forceUpdate(items.at(i));
}
+void QQuickWindowRenderTarget::reset(QRhi *rhi)
+{
+ if (rhi && owns) {
+ delete renderTarget;
+ delete rpDesc;
+ delete texture;
+ delete depthStencil;
+ }
+
+ renderTarget = nullptr;
+ rpDesc = nullptr;
+ texture = nullptr;
+ depthStencil = nullptr;
+ owns = false;
+}
+
+void QQuickWindowPrivate::ensureCustomRenderTarget()
+{
+ // resolve() can be expensive when importing an existing native texture, so
+ // it is important to only do it when the QQuickRenderTarget* was really changed
+ if (!redirect.renderTargetDirty || !rhi)
+ return;
+
+ redirect.renderTargetDirty = false;
+
+ redirect.rt.reset(rhi);
+
+ // a default constructed QQuickRenderTarget means no redirection
+ if (customRenderTarget.isNull())
+ return;
+
+ QQuickRenderTargetPrivate::get(&customRenderTarget)->resolve(rhi, &redirect.rt);
+}
+
+void QQuickWindowPrivate::setCustomCommandBuffer(QRhiCommandBuffer *cb)
+{
+ // ownership not transferred
+ redirect.commandBuffer = cb;
+}
+
void QQuickWindowPrivate::syncSceneGraph()
{
Q_Q(QQuickWindow);
+ ensureCustomRenderTarget();
+
// Calculate the dpr the same way renderSceneGraph() will.
qreal devicePixelRatio = q->effectiveDevicePixelRatio();
- if (renderTargetId && !QQuickRenderControl::renderWindowFor(q))
+ const bool hasCustomRenderTarget = customRenderTargetGl.renderTargetId || redirect.rt.renderTarget;
+ if (hasCustomRenderTarget && !QQuickRenderControl::renderWindowFor(q))
devicePixelRatio = 1;
- context->prepareSync(devicePixelRatio, rhi ? swapchain->currentFrameCommandBuffer() : nullptr);
+ QRhiCommandBuffer *cb = nullptr;
+ if (rhi) {
+ if (redirect.commandBuffer)
+ cb = redirect.commandBuffer;
+ else
+ cb = swapchain->currentFrameCommandBuffer();
+ }
+ context->prepareSync(devicePixelRatio, cb);
animationController->beforeNodeSync();
@@ -484,11 +535,32 @@ void QQuickWindowPrivate::renderSceneGraph(const QSize &size, const QSize &surfa
return;
if (rhi) {
- // ### no offscreen ("renderTargetId") support yet
- context->beginNextRhiFrame(renderer,
- swapchain->currentFrameRenderTarget(),
- rpDescForSwapchain,
- swapchain->currentFrameCommandBuffer(),
+ ensureCustomRenderTarget();
+ QRhiRenderTarget *rt;
+ QRhiRenderPassDescriptor *rp;
+ QRhiCommandBuffer *cb;
+ if (redirect.rt.renderTarget) {
+ rt = redirect.rt.renderTarget;
+ rp = rt->renderPassDescriptor();
+ if (!rp) {
+ qWarning("Custom render target is set but no renderpass descriptor has been provided.");
+ return;
+ }
+ cb = redirect.commandBuffer;
+ if (!cb) {
+ qWarning("Custom render target is set but no command buffer has been provided.");
+ return;
+ }
+ } else {
+ if (!swapchain) {
+ qWarning("QQuickWindow: No render target (neither swapchain nor custom target was provided)");
+ return;
+ }
+ rt = swapchain->currentFrameRenderTarget();
+ rp = rpDescForSwapchain;
+ cb = swapchain->currentFrameCommandBuffer();
+ }
+ context->beginNextRhiFrame(renderer, rt, rp, cb,
emitBeforeRenderPassRecording,
emitAfterRenderPassRecording,
q);
@@ -503,18 +575,28 @@ void QQuickWindowPrivate::renderSceneGraph(const QSize &size, const QSize &surfa
emit q->beforeRendering();
runAndClearJobs(&beforeRenderingJobs);
if (!customRenderStage || !customRenderStage->render()) {
+ QSGAbstractRenderer::MatrixTransformFlags matrixFlags;
+ const bool flipY = rhi ? !rhi->isYUpInNDC() : false;
+ if (flipY)
+ matrixFlags |= QSGAbstractRenderer::MatrixTransformFlipY;
int fboId = 0;
const qreal devicePixelRatio = q->effectiveDevicePixelRatio();
- if (renderTargetId) {
- QRect rect(QPoint(0, 0), renderTargetSize);
- fboId = renderTargetId;
+ const bool hasCustomRenderTarget = customRenderTargetGl.renderTargetId || redirect.rt.renderTarget;
+ if (hasCustomRenderTarget) {
+ QRect rect;
+ if (redirect.rt.renderTarget) {
+ rect = QRect(QPoint(0, 0), redirect.rt.renderTarget->pixelSize());
+ } else {
+ fboId = customRenderTargetGl.renderTargetId;
+ rect = QRect(QPoint(0, 0), customRenderTargetGl.renderTargetSize);
+ }
renderer->setDeviceRect(rect);
renderer->setViewportRect(rect);
if (QQuickRenderControl::renderWindowFor(q)) {
- renderer->setProjectionMatrixToRect(QRect(QPoint(0, 0), size));
+ renderer->setProjectionMatrixToRect(QRect(QPoint(0, 0), size), matrixFlags);
renderer->setDevicePixelRatio(devicePixelRatio);
} else {
- renderer->setProjectionMatrixToRect(QRect(QPoint(0, 0), rect.size()));
+ renderer->setProjectionMatrixToRect(QRect(QPoint(0, 0), rect.size()), matrixFlags);
renderer->setDevicePixelRatio(1);
}
} else {
@@ -530,10 +612,6 @@ void QQuickWindowPrivate::renderSceneGraph(const QSize &size, const QSize &surfa
QRect rect(QPoint(0, 0), pixelSize);
renderer->setDeviceRect(rect);
renderer->setViewportRect(rect);
- const bool flipY = rhi ? !rhi->isYUpInNDC() : false;
- QSGAbstractRenderer::MatrixTransformFlags matrixFlags;
- if (flipY)
- matrixFlags |= QSGAbstractRenderer::MatrixTransformFlipY;
renderer->setProjectionMatrixToRect(QRectF(QPoint(0, 0), logicalSize), matrixFlags);
renderer->setDevicePixelRatio(devicePixelRatio);
}
@@ -589,8 +667,6 @@ QQuickWindowPrivate::QQuickWindowPrivate()
, allowChildEventFiltering(true)
, allowDoubleClick(true)
, lastFocusReason(Qt::OtherFocusReason)
- , renderTarget(nullptr)
- , renderTargetId(0)
, vaoHelper(nullptr)
, incubationController(nullptr)
, hasActiveSwapchain(false)
@@ -604,6 +680,7 @@ QQuickWindowPrivate::QQuickWindowPrivate()
QQuickWindowPrivate::~QQuickWindowPrivate()
{
+ redirect.rt.reset(rhi);
delete customRenderStage;
if (QQmlInspectorService *service = QQmlDebugConnector::service<QQmlInspectorService>())
service->removeWindow(q_func());
@@ -652,7 +729,7 @@ void QQuickWindowPrivate::init(QQuickWindow *c, QQuickRenderControl *control)
q->setSurfaceType(windowManager ? windowManager->windowSurfaceType() : QSurface::OpenGLSurface);
q->setFormat(sg->defaultSurfaceFormat());
#if QT_CONFIG(vulkan)
- if (q->surfaceType() == QSurface::VulkanSurface)
+ if (!renderControl && q->surfaceType() == QSurface::VulkanSurface)
q->setVulkanInstance(QSGRhiSupport::vulkanInstance());
#endif
@@ -4028,13 +4105,13 @@ void QQuickWindow::setRenderTarget(QOpenGLFramebufferObject *fbo)
return;
}
- d->renderTarget = fbo;
+ d->customRenderTargetGl.renderTarget = fbo;
if (fbo) {
- d->renderTargetId = fbo->handle();
- d->renderTargetSize = fbo->size();
+ d->customRenderTargetGl.renderTargetId = fbo->handle();
+ d->customRenderTargetGl.renderTargetSize = fbo->size();
} else {
- d->renderTargetId = 0;
- d->renderTargetSize = QSize();
+ d->customRenderTargetGl.renderTargetId = 0;
+ d->customRenderTargetGl.renderTargetSize = QSize();
}
}
#endif
@@ -4069,11 +4146,11 @@ void QQuickWindow::setRenderTarget(uint fboId, const QSize &size)
return;
}
- d->renderTargetId = fboId;
- d->renderTargetSize = size;
+ d->customRenderTargetGl.renderTargetId = fboId;
+ d->customRenderTargetGl.renderTargetSize = size;
// Unset any previously set instance...
- d->renderTarget = nullptr;
+ d->customRenderTargetGl.renderTarget = nullptr;
}
@@ -4083,7 +4160,7 @@ void QQuickWindow::setRenderTarget(uint fboId, const QSize &size)
uint QQuickWindow::renderTargetId() const
{
Q_D(const QQuickWindow);
- return d->renderTargetId;
+ return d->customRenderTargetGl.renderTargetId;
}
/*!
@@ -4092,11 +4169,9 @@ uint QQuickWindow::renderTargetId() const
QSize QQuickWindow::renderTargetSize() const
{
Q_D(const QQuickWindow);
- return d->renderTargetSize;
+ return d->customRenderTargetGl.renderTargetSize;
}
-
-
#if QT_CONFIG(opengl)
/*!
Returns the render target for this window.
@@ -4113,11 +4188,84 @@ QSize QQuickWindow::renderTargetSize() const
QOpenGLFramebufferObject *QQuickWindow::renderTarget() const
{
Q_D(const QQuickWindow);
- return d->renderTarget;
+ return d->customRenderTargetGl.renderTarget;
}
#endif
/*!
+ Sets the render target for this window to be \a target.
+
+ A QQuickRenderTarget serves as an opaque handle for a renderable native
+ object, most commonly a 2D texture, and associated metadata, such as the
+ size in pixels.
+
+ A default constructed QQuickRenderTarget means no redirection. A valid
+ \a target, created via one of the static QQuickRenderTarget factory functions,
+ on the other hand, enables redirection of the rendering of the Qt Quick
+ scene: it will no longer target the color buffers for the surface
+ associated with the window, but rather the textures or other graphics
+ objects specified in \a target.
+
+ For example, assuming the scenegraph is using Vulkan to render, one can
+ redirect its output into a \c VkImage. For graphics APIs like Vulkan, the
+ image layout must be provided as well. QQuickRenderTarget instances are
+ implicitly shared and are copyable and can be passed by value. They do not
+ own the associated native objects (such as, the VkImage in the example),
+ however.
+
+ \badcode
+ QQuickRenderTarget rt = QQuickRenderTarget::fromNativeTexture({ &vulkanImage, VK_IMAGE_LAYOUT_PREINITIALIZED }, pixelSize);
+ quickWindow->setRenderTarget(rt);
+ \endcode
+
+ This function is very often used in combination with QQuickRenderControl
+ and an invisible QQuickWindow, in order to render Qt Quick content into a
+ texture, without creating an on-screen native window for this QQuickWindow.
+
+ When the desired target, or associated data, such as the size, changes,
+ call this function with a new QQuickRenderTarget. Constructing
+ QQuickRenderTarget instances and calling this function is cheap, but be
+ aware that setting a new \a target with a different native object or other
+ data may lead to potentially expensive initialization steps when the
+ scenegraph is about to render the next frame. Therefore change the target
+ only when necessary.
+
+ \note This function should not be used when using the \c software backend.
+ Instead, use grabWindow() to render the content into a QImage.
+
+ \note The window does not take ownership of any native objects referenced
+ in \a target.
+
+ \note It is the caller's responsibility to ensure the native objects
+ referred to in \a target are valid for the scenegraph renderer too. For
+ instance, with Vulkan, Metal, and Direct3D this implies that the texture or
+ image is created on the same graphics device that is used by the scenegraph
+ internally. This is often achieved by using this function in combination
+ with setGraphicsDevice().
+
+ \note With graphics APIs where relevant, the application must pay attention
+ to image layout transitions performed by the scenegraph. For example, once
+ a VkImage is associated with the scenegraph by calling this function, its
+ layout will transition to \c VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL when
+ rendering a frame.
+
+ \warning This function can only be called from the thread doing the
+ rendering.
+
+ \since 6.0
+
+ \sa QQuickRenderControl, setGraphicsDevice(), setSceneGraphBackend()
+ */
+void QQuickWindow::setRenderTarget(const QQuickRenderTarget &target)
+{
+ Q_D(QQuickWindow);
+ if (target != d->customRenderTarget) {
+ d->customRenderTarget = target;
+ d->redirect.renderTargetDirty = true;
+ }
+}
+
+/*!
Grabs the contents of the window and returns it as an image.
It is possible to call the grabWindow() function when the window is not
@@ -4125,6 +4273,14 @@ QOpenGLFramebufferObject *QQuickWindow::renderTarget() const
and has a valid size and that no other QQuickWindow instances are rendering
in the same process.
+ \note When using this window in combination with QQuickRenderControl, the
+ result of this function is an empty image, unless the \c software backend
+ is in use. This is because when redirecting the output to an
+ application-managed graphics resource (such as, a texture) by using
+ QQuickRenderControl and setRenderTarget(), the application is better suited
+ for managing and executing an eventual read back operation, since it is in
+ full control of the resource to begin with.
+
\warning Calling this function will cause performance problems.
\warning This function can only be called from the GUI thread.
@@ -4141,12 +4297,8 @@ QImage QQuickWindow::grabWindow()
if (!isVisible() && !d->renderControl) {
if (d->rhi) {
- // ### we may need a full offscreen round when non-exposed...
-
- if (d->renderControl)
- return d->renderControl->grab();
- else if (d->windowManager)
- return d->windowManager->grab(this);
+ if (d->windowManager)
+ return d->windowManager->grab(this); // ### we may need a full offscreen round when non-exposed?
return QImage();
}
@@ -4184,7 +4336,7 @@ QImage QQuickWindow::grabWindow()
}
if (d->renderControl)
- return d->renderControl->grab();
+ return QQuickRenderControlPrivate::get(d->renderControl)->grab();
else if (d->windowManager)
return d->windowManager->grab(this);
return QImage();
@@ -5648,7 +5800,11 @@ QSGRendererInterface *QQuickWindow::rendererInterface() const
loaded plugins.
\note The call to the function must happen before constructing the first
- QQuickWindow in the application. It cannot be changed afterwards.
+ QQuickWindow in the application. The graphics API cannot be changed
+ afterwards. When used in combination with QQuickRenderControl, this rule is
+ relaxed: it is possible to change the graphics API, but only when all
+ existing QQuickRenderControl and QQuickWindow instances have been
+ destroyed.
If the selected backend is invalid or an error occurs, the default backend
(OpenGL or software, depending on the Qt configuration) is used.
@@ -5709,6 +5865,65 @@ QString QQuickWindow::sceneGraphBackend()
}
/*!
+ Sets the graphics device objects for this window. The scenegraph will use
+ existing device, physical device, and other objects specified by \a device
+ instead of creating new ones.
+
+ This function is very often used in combination with QQuickRenderControl
+ and setRenderTarget(), in order to redirect Qt Quick rendering into a
+ texture.
+
+ A default constructed QQuickGraphicsDevice does not change the default
+ behavior in any way. Once a \a device created via one of the
+ QQuickGraphicsDevice factory functions, such as,
+ QQuickGraphicsDevice::fromDeviceObjects(), is passed in, and the scenegraph
+ uses a matching graphics API (with the example of fromDeviceObjects(), that
+ would be Vulkan), the scenegraph will use the existing device objects (such
+ as, the \c VkPhysicalDevice, \c VkDevice, and graphics queue family index,
+ in case of Vulkan) encapsulated by the QQuickGraphicsDevice. This allows
+ using the same device, and so sharing resources, such as buffers and
+ textures, between Qt Quick and native rendering engines.
+
+ \warning This function can only be called before initializing the
+ scenegraph and will have no effect if called afterwards. In practice this
+ typically means calling it right before QQuickRenderControl::initialize().
+
+ As an example, this time with Direct3D, the typical usage is expected to be
+ the following:
+
+ \badcode
+ // native graphics resources set up by a custom D3D rendering engine
+ ID3D11Device *device;
+ ID3D11DeviceContext *context;
+ ID3D11Texture2D *texture;
+ ...
+ // now to redirect Qt Quick content into 'texture' we could do the following:
+ QQuickRenderControl *renderControl = new QQuickRenderControl;
+ QQuickWindow *window = new QQuickWindow(renderControl); // this window will never be shown on-screen
+ ...
+ window->setGraphicsDevice(QQuickGraphicsDevice::fromDeviceAndContext(device, context));
+ renderControl->initialize();
+ window->setRenderTarget(QQuickRenderTarget::fromNativeTexture({ &texture, 0 }, textureSize);
+ ...
+ \endcode
+
+ The key aspect of using this function is to ensure that resources or
+ handles to resources, such as \c texture in the above example, are visible
+ to and usable by both the external rendering engine and the scenegraph
+ renderer. This requires using the same graphics device (or with OpenGL,
+ OpenGL context).
+
+ \since 6.0
+
+ \sa QQuickRenderControl, setRenderTarget(), setSceneGraphBackend()
+ */
+void QQuickWindow::setGraphicsDevice(const QQuickGraphicsDevice &device)
+{
+ Q_D(QQuickWindow);
+ d->customDeviceObjects = device;
+}
+
+/*!
Creates a simple rectangle node. When the scenegraph is not initialized, the return value is null.
This is cross-backend alternative to constructing a QSGSimpleRectNode directly.
diff --git a/src/quick/items/qquickwindow.h b/src/quick/items/qquickwindow.h
index 1331d036e1..a2278a8df0 100644
--- a/src/quick/items/qquickwindow.h
+++ b/src/quick/items/qquickwindow.h
@@ -66,7 +66,8 @@ class QQuickRenderControl;
class QSGRectangleNode;
class QSGImageNode;
class QSGNinePatchNode;
-class QRhi;
+class QQuickRenderTarget;
+class QQuickGraphicsDevice;
class Q_QUICK_EXPORT QQuickWindow : public QWindow
{
@@ -131,6 +132,8 @@ public:
#endif
QImage grabWindow();
+
+ // ### Qt 6 remove all these 5 functions. Replaced by setRenderTarget(QQuickRenderTarget*).
#if QT_CONFIG(opengl)
void setRenderTarget(QOpenGLFramebufferObject *fbo);
QOpenGLFramebufferObject *renderTarget() const;
@@ -138,8 +141,11 @@ public:
void setRenderTarget(uint fboId, const QSize &size);
uint renderTargetId() const;
QSize renderTargetSize() const;
+
+ void setRenderTarget(const QQuickRenderTarget &target);
+
#if QT_CONFIG(opengl)
- void resetOpenGLState();
+ void resetOpenGLState(); // ### Qt 6 remove
#endif
struct GraphicsStateInfo {
int currentFrameSlot;
@@ -178,13 +184,14 @@ public:
static bool hasDefaultAlphaBuffer();
static void setDefaultAlphaBuffer(bool useAlpha);
- void setPersistentOpenGLContext(bool persistent);
+ void setPersistentOpenGLContext(bool persistent); // ### Qt 6 is this relevant / usable anymore?
bool isPersistentOpenGLContext() const;
- void setPersistentSceneGraph(bool persistent);
+ void setPersistentSceneGraph(bool persistent); // ### Qt 6 is this relevant / usable anymore?
bool isPersistentSceneGraph() const;
- QOpenGLContext *openglContext() const;
+ QOpenGLContext *openglContext() const; // ### Qt 6 consider if this is kept or not
+
bool isSceneGraphInitialized() const;
void scheduleRenderJob(QRunnable *job, RenderStage schedule);
@@ -197,6 +204,8 @@ public:
static void setSceneGraphBackend(const QString &backend);
static QString sceneGraphBackend();
+ void setGraphicsDevice(const QQuickGraphicsDevice &device);
+
QSGRectangleNode *createRectangleNode() const;
QSGImageNode *createImageNode() const;
QSGNinePatchNode *createNinePatchNode() const;
@@ -206,7 +215,7 @@ public:
Q_SIGNALS:
void frameSwapped();
- Q_REVISION(2, 2) void openglContextCreated(QOpenGLContext *context);
+ Q_REVISION(2, 2) void openglContextCreated(QOpenGLContext *context); // ### Qt 6 remove
void sceneGraphInitialized();
void sceneGraphInvalidated();
void beforeSynchronizing();
diff --git a/src/quick/items/qquickwindow_p.h b/src/quick/items/qquickwindow_p.h
index a77ce30a89..ba8691cba3 100644
--- a/src/quick/items/qquickwindow_p.h
+++ b/src/quick/items/qquickwindow_p.h
@@ -57,6 +57,8 @@
#include <QtQuick/private/qsgcontext_p.h>
#include <QtQuick/private/qquickpaletteproviderprivatebase_p.h>
+#include <QtQuick/private/qquickrendertarget_p.h>
+#include <QtQuick/private/qquickgraphicsdevice_p.h>
#include <QtCore/qthread.h>
#include <QtCore/qmutex.h>
@@ -84,6 +86,7 @@ class QRhi;
class QRhiSwapChain;
class QRhiRenderBuffer;
class QRhiRenderPassDescriptor;
+class QRhiTexture;
//Make it easy to identify and customize the root item if needed
class Q_QUICK_PRIVATE_EXPORT QQuickRootItem : public QQuickItem
@@ -104,6 +107,17 @@ public:
virtual bool swap() = 0;
};
+class QQuickWindowRenderTarget
+{
+public:
+ void reset(QRhi *rhi);
+ QRhiRenderTarget *renderTarget = nullptr;
+ QRhiRenderPassDescriptor *rpDesc = nullptr;
+ QRhiTexture *texture = nullptr;
+ QRhiRenderBuffer *depthStencil = nullptr;
+ bool owns = false;
+};
+
class Q_QUICK_PRIVATE_EXPORT QQuickWindowPrivate
: public QWindowPrivate
, public QQuickPaletteProviderPrivateBase<QQuickWindow, QQuickWindowPrivate>
@@ -218,6 +232,9 @@ public:
void dirtyItem(QQuickItem *);
void cleanup(QSGNode *);
+ void ensureCustomRenderTarget();
+ void setCustomCommandBuffer(QRhiCommandBuffer *cb);
+
void polishItems();
void forcePolish();
void syncSceneGraph();
@@ -277,11 +294,26 @@ public:
Qt::FocusReason lastFocusReason;
- QOpenGLFramebufferObject *renderTarget;
- uint renderTargetId;
- QSize renderTargetSize;
+ // Storage for setRenderTarget(QQuickRenderTarget).
+ // Gets baked into redirect.renderTarget by ensureCustomRenderTarget() when rendering the next frame.
+ QQuickRenderTarget customRenderTarget;
+
+ struct Redirect {
+ QRhiCommandBuffer *commandBuffer = nullptr;
+ QQuickWindowRenderTarget rt;
+ bool renderTargetDirty = false;
+ } redirect;
+
+ QQuickGraphicsDevice customDeviceObjects;
+
+ // ### Qt 6 remove
+ struct {
+ QOpenGLFramebufferObject *renderTarget = nullptr;
+ uint renderTargetId = 0;
+ QSize renderTargetSize;
+ } customRenderTargetGl;
- QOpenGLVertexArrayObjectHelper *vaoHelper;
+ QOpenGLVertexArrayObjectHelper *vaoHelper; // ### Qt 6 remove
mutable QQuickWindowIncubationController *incubationController;
diff --git a/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp b/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp
index 5a281cdd4a..5dac796f2a 100644
--- a/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp
+++ b/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp
@@ -4116,6 +4116,8 @@ void Renderer::renderBatches()
if (m_renderPassRecordingCallbacks.start)
m_renderPassRecordingCallbacks.start(m_renderPassRecordingCallbacks.userData);
+ cb->debugMarkBegin(QByteArrayLiteral("Qt Quick scene render"));
+
for (int i = 0, ie = opaqueRenderBatches.count(); i != ie; ++i) {
PreparedRenderBatch *renderBatch = &opaqueRenderBatches[i];
if (renderBatch->batch->merged)
@@ -4137,6 +4139,8 @@ void Renderer::renderBatches()
if (m_currentShader)
setActiveRhiShader(nullptr, nullptr);
+ cb->debugMarkEnd();
+
if (m_renderPassRecordingCallbacks.end)
m_renderPassRecordingCallbacks.end(m_renderPassRecordingCallbacks.userData);
diff --git a/src/quick/scenegraph/qsgrenderloop.cpp b/src/quick/scenegraph/qsgrenderloop.cpp
index 424d1b9ea8..a49f4a2263 100644
--- a/src/quick/scenegraph/qsgrenderloop.cpp
+++ b/src/quick/scenegraph/qsgrenderloop.cpp
@@ -126,7 +126,7 @@ void QSGRenderLoop::cleanup()
s_instance = nullptr;
#ifdef ENABLE_DEFAULT_BACKEND
- QSGRhiSupport::instance()->cleanup();
+ QSGRhiSupport::cleanupVulkanInstance();
QSGRhiProfileConnection::instance()->cleanup();
#endif
}
diff --git a/src/quick/scenegraph/qsgrhisupport.cpp b/src/quick/scenegraph/qsgrhisupport.cpp
index 29671f8a10..b9a81789e0 100644
--- a/src/quick/scenegraph/qsgrhisupport.cpp
+++ b/src/quick/scenegraph/qsgrhisupport.cpp
@@ -39,7 +39,8 @@
#include "qsgrhisupport_p.h"
#include "qsgdefaultrendercontext_p.h"
-#include <QtGui/qwindow.h>
+#include <QtQuick/private/qquickitem_p.h>
+#include <QtQuick/private/qquickwindow_p.h>
#if QT_CONFIG(vulkan)
#include <QtGui/qvulkaninstance.h>
@@ -88,7 +89,7 @@ QVulkanInstance *QSGRhiSupport::vulkanInstance()
#endif
}
-void QSGRhiSupport::cleanup()
+void QSGRhiSupport::cleanupVulkanInstance()
{
#if QT_CONFIG(vulkan)
delete s_vulkanInstance;
@@ -97,7 +98,7 @@ void QSGRhiSupport::cleanup()
}
QSGRhiSupport::QSGRhiSupport()
- : m_set(false),
+ : m_settingsApplied(false),
m_enableRhi(false),
m_debugLayer(false),
m_profile(false),
@@ -108,7 +109,7 @@ QSGRhiSupport::QSGRhiSupport()
void QSGRhiSupport::applySettings()
{
- m_set = true;
+ m_settingsApplied = true;
// This is also done when creating the renderloop but we may be before that
// in case we get here due to a setScenegraphBackend() -> configure() early
@@ -214,10 +215,6 @@ void QSGRhiSupport::configure(QSGRendererInterface::GraphicsApi api)
{
Q_ASSERT(QSGRendererInterface::isApiRhiBased(api));
QSGRhiSupport *inst = staticInst();
- if (inst->m_set) {
- qWarning("QRhi is already configured, request ignored");
- return;
- }
inst->m_requested.valid = true;
inst->m_requested.api = api;
inst->m_requested.rhi = true;
@@ -227,7 +224,7 @@ void QSGRhiSupport::configure(QSGRendererInterface::GraphicsApi api)
QSGRhiSupport *QSGRhiSupport::instance()
{
QSGRhiSupport *inst = staticInst();
- if (!inst->m_set)
+ if (!inst->m_settingsApplied)
inst->applySettings();
return inst;
}
@@ -467,11 +464,10 @@ QOffscreenSurface *QSGRhiSupport::maybeCreateOffscreenSurface(QWindow *window)
}
// must be called on the render thread
-QRhi *QSGRhiSupport::createRhi(QWindow *window, QOffscreenSurface *offscreenSurface)
+QRhi *QSGRhiSupport::createRhi(QQuickWindow *window, QOffscreenSurface *offscreenSurface)
{
-#if !QT_CONFIG(opengl) && !QT_CONFIG(vulkan)
- Q_UNUSED(window);
-#endif
+ const QQuickGraphicsDevice &customDev(QQuickWindowPrivate::get(window)->customDeviceObjects);
+ const QQuickGraphicsDevicePrivate *customDevD = QQuickGraphicsDevicePrivate::get(&customDev);
QRhi *rhi = nullptr;
@@ -493,7 +489,14 @@ QRhi *QSGRhiSupport::createRhi(QWindow *window, QOffscreenSurface *offscreenSurf
rhiParams.format = format;
rhiParams.fallbackSurface = offscreenSurface;
rhiParams.window = window;
- rhi = QRhi::create(backend, &rhiParams, flags);
+ if (customDevD->type == QQuickGraphicsDevicePrivate::Type::OpenGLContext) {
+ QRhiGles2NativeHandles importDev;
+ importDev.context = customDevD->u.context;
+ qCDebug(QSG_LOG_INFO, "Using existing QOpenGLContext %p", importDev.context);
+ rhi = QRhi::create(backend, &rhiParams, flags, &importDev);
+ } else {
+ rhi = QRhi::create(backend, &rhiParams, flags);
+ }
}
#else
Q_UNUSED(offscreenSurface);
@@ -504,8 +507,19 @@ QRhi *QSGRhiSupport::createRhi(QWindow *window, QOffscreenSurface *offscreenSurf
rhiParams.inst = window->vulkanInstance();
if (!rhiParams.inst)
qWarning("No QVulkanInstance set for QQuickWindow, this is wrong.");
- rhiParams.window = window;
- rhi = QRhi::create(backend, &rhiParams, flags);
+ if (window->handle()) // only used for vkGetPhysicalDeviceSurfaceSupportKHR and that implies having a valid native window
+ rhiParams.window = window;
+ if (customDevD->type == QQuickGraphicsDevicePrivate::Type::DeviceObjects) {
+ QRhiVulkanNativeHandles importDev;
+ importDev.physDev = reinterpret_cast<VkPhysicalDevice>(customDevD->u.deviceObjects.physicalDevice);
+ importDev.dev = reinterpret_cast<VkDevice>(customDevD->u.deviceObjects.device);
+ importDev.gfxQueueFamilyIdx = customDevD->u.deviceObjects.queueFamilyIndex;
+ qCDebug(QSG_LOG_INFO, "Using existing native Vulkan physical device %p device %p graphics queue family index %d",
+ importDev.physDev, importDev.dev, importDev.gfxQueueFamilyIdx);
+ rhi = QRhi::create(backend, &rhiParams, flags, &importDev);
+ } else {
+ rhi = QRhi::create(backend, &rhiParams, flags);
+ }
}
#endif
#ifdef Q_OS_WIN
@@ -516,13 +530,31 @@ QRhi *QSGRhiSupport::createRhi(QWindow *window, QOffscreenSurface *offscreenSurf
rhiParams.framesUntilKillingDeviceViaTdr = m_killDeviceFrameCount;
rhiParams.repeatDeviceKill = true;
}
- rhi = QRhi::create(backend, &rhiParams, flags);
+ if (customDevD->type == QQuickGraphicsDevicePrivate::Type::DeviceAndContext) {
+ QRhiD3D11NativeHandles importDev;
+ importDev.dev = customDevD->u.deviceAndContext.device;
+ importDev.context = customDevD->u.deviceAndContext.context;
+ qCDebug(QSG_LOG_INFO, "Using existing native D3D11 device %p and context %p",
+ importDev.dev, importDev.context);
+ rhi = QRhi::create(backend, &rhiParams, flags, &importDev);
+ } else {
+ rhi = QRhi::create(backend, &rhiParams, flags);
+ }
}
#endif
#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
if (backend == QRhi::Metal) {
QRhiMetalInitParams rhiParams;
- rhi = QRhi::create(backend, &rhiParams, flags);
+ if (customDevD->type == QQuickGraphicsDevicePrivate::Type::DeviceAndCommandQueue) {
+ QRhiMetalNativeHandles importDev;
+ importDev.dev = customDevD->u.deviceAndCommandQueue.device;
+ importDev.cmdQueue = customDevD->u.deviceAndCommandQueue.cmdQueue;
+ qCDebug(QSG_LOG_INFO, "Using existing native Metal device %p and command queue %p",
+ importDev.dev, importDev.cmdQueue);
+ rhi = QRhi::create(backend, &rhiParams, flags, &importDev);
+ } else {
+ rhi = QRhi::create(backend, &rhiParams, flags);
+ }
}
#endif
diff --git a/src/quick/scenegraph/qsgrhisupport_p.h b/src/quick/scenegraph/qsgrhisupport_p.h
index 0a95a09ad2..601dd07895 100644
--- a/src/quick/scenegraph/qsgrhisupport_p.h
+++ b/src/quick/scenegraph/qsgrhisupport_p.h
@@ -91,7 +91,7 @@ class QOffscreenSurface;
// Opting in/out of QRhi and choosing the default/requested backend is managed
// by this singleton. This is because this information may be needed before
// creating a render loop. A well-written render loop sets up its QRhi and
-// related machinery based on the settings queriable from here.
+// related machinery using the helper functions in here.
//
// cleanup() must be called to perform global (not per thread) cleanup, such
// as, destroying the QVulkanInstance (if one was created in vulkanInstance()).
@@ -99,13 +99,13 @@ class QOffscreenSurface;
// In addition, the class provides handy conversion and query stuff for the
// renderloop and the QSGRendererInterface implementations.
//
-class QSGRhiSupport
+class Q_QUICK_PRIVATE_EXPORT QSGRhiSupport
{
public:
static void configure(QSGRendererInterface::GraphicsApi api);
static QSGRhiSupport *instance();
static QVulkanInstance *vulkanInstance();
- void cleanup();
+ static void cleanupVulkanInstance();
bool isRhiEnabled() const { return m_enableRhi; }
QRhi::Implementation rhiBackend() const { return m_rhiBackend; }
@@ -124,7 +124,7 @@ public:
int chooseSampleCountForWindowWithRhi(QWindow *window, QRhi *rhi);
QOffscreenSurface *maybeCreateOffscreenSurface(QWindow *window);
- QRhi *createRhi(QWindow *window, QOffscreenSurface *offscreenSurface);
+ QRhi *createRhi(QQuickWindow *window, QOffscreenSurface *offscreenSurface);
QImage grabAndBlockInCurrentFrame(QRhi *rhi, QRhiSwapChain *swapchain);
@@ -141,7 +141,7 @@ private:
} m_requested;
QRhi::Implementation m_rhiBackend = QRhi::Null;
int m_killDeviceFrameCount;
- uint m_set : 1;
+ uint m_settingsApplied : 1;
uint m_enableRhi : 1;
uint m_debugLayer : 1;
uint m_profile : 1;
diff --git a/src/quickwidgets/qquickwidget.cpp b/src/quickwidgets/qquickwidget.cpp
index 59d4a89fbf..f3d26e4a73 100644
--- a/src/quickwidgets/qquickwidget.cpp
+++ b/src/quickwidgets/qquickwidget.cpp
@@ -383,7 +383,7 @@ QImage QQuickWidgetPrivate::grabFramebuffer()
context->makeCurrent(offscreenSurface);
#endif
}
- return renderControl->grab();
+ return offscreenWindow->grabWindow();
}
// Intentionally not overriding the QQuickWindow's focusObject.
diff --git a/tests/auto/quick/qquickrendercontrol/tst_qquickrendercontrol.cpp b/tests/auto/quick/qquickrendercontrol/tst_qquickrendercontrol.cpp
index 4bab4e345a..29e0c6caac 100644
--- a/tests/auto/quick/qquickrendercontrol/tst_qquickrendercontrol.cpp
+++ b/tests/auto/quick/qquickrendercontrol/tst_qquickrendercontrol.cpp
@@ -38,6 +38,8 @@
#include <QQuickWindow>
#include <QQuickRenderControl>
+#include <QQuickRenderTarget>
+#include <QQuickGraphicsDevice>
#include <QQuickItem>
#include <QQmlEngine>
#include <QQmlComponent>
@@ -47,19 +49,13 @@
#include <QtGui/private/qguiapplication_p.h>
#include <QtGui/qpa/qplatformintegration.h>
-class tst_RenderControl : public QQmlDataTest
-{
- Q_OBJECT
-
-private slots:
- void initTestCase();
- void renderAndReadBack();
-};
+#include <QtGui/private/qrhi_p.h>
+#include <QtQuick/private/qquickrendercontrol_p.h>
-void tst_RenderControl::initTestCase()
-{
- QQmlDataTest::initTestCase();
-}
+#if QT_CONFIG(vulkan)
+#include <QVulkanInstance>
+#include <QVulkanFunctions>
+#endif
class AnimationDriver : public QAnimationDriver
{
@@ -82,14 +78,50 @@ private:
qint64 m_elapsed = 0;
};
+class tst_RenderControl : public QQmlDataTest
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void renderAndReadBackDirectOpenGL();
+ void renderAndReadBackWithRhi_data();
+ void renderAndReadBackWithRhi();
+ void renderAndReadBackWithVulkanNative();
+
+private:
+#if QT_CONFIG(vulkan)
+ QVulkanInstance vulkanInstance;
+#endif
+ AnimationDriver *animDriver;
+};
-void tst_RenderControl::renderAndReadBack()
+void tst_RenderControl::initTestCase()
{
+ QQmlDataTest::initTestCase();
+
+#if QT_CONFIG(vulkan)
+ vulkanInstance.setLayers({ "VK_LAYER_LUNARG_standard_validation" });
+ vulkanInstance.create(); // may fail, that's sometimes ok, we'll check for it later
+#endif
+
+ // Install the animation driver once, globally, instead of in the
+ // individual tests. This tends to work better as it avoids the need to
+ // have more complicated logic when calling advance().
+
static const int ANIM_ADVANCE_PER_FRAME = 16; // milliseconds
- QScopedPointer<AnimationDriver> animDriver(new AnimationDriver(ANIM_ADVANCE_PER_FRAME));
+ animDriver = new AnimationDriver(ANIM_ADVANCE_PER_FRAME);
animDriver->install();
+}
+
+void tst_RenderControl::cleanupTestCase()
+{
+ delete animDriver;
+}
- // ### Qt 6: migrate this to OpenGL-on-RHI
+void tst_RenderControl::renderAndReadBackDirectOpenGL() // ### Qt 6 remove
+{
#if QT_CONFIG(opengl)
if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::OpenGL))
QSKIP("Skipping due to platform not supporting OpenGL at run time");
@@ -197,6 +229,548 @@ void tst_RenderControl::renderAndReadBack()
#endif
}
+void tst_RenderControl::renderAndReadBackWithRhi_data()
+{
+ QTest::addColumn<QSGRendererInterface::GraphicsApi>("api");
+
+#if QT_CONFIG(opengl)
+ QTest::newRow("OpenGL") << QSGRendererInterface::OpenGLRhi;
+#endif
+#if QT_CONFIG(vulkan)
+ QTest::newRow("Vulkan") << QSGRendererInterface::VulkanRhi;
+#endif
+#ifdef Q_OS_WIN
+ QTest::newRow("D3D11") << QSGRendererInterface::Direct3D11Rhi;
+#endif
+#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+ QTest::newRow("Metal") << QSGRendererInterface::MetalRhi;
+#endif
+}
+
+void tst_RenderControl::renderAndReadBackWithRhi()
+{
+ QFETCH(QSGRendererInterface::GraphicsApi, api);
+#if QT_CONFIG(vulkan)
+ if (api == QSGRendererInterface::VulkanRhi && !vulkanInstance.isValid())
+ QSKIP("Skipping Vulkan-based QRhi readback test due to failing to create a VkInstance");
+#endif
+
+ // Changing the graphics api is not possible once a QQuickWindow et al is
+ // created, however we do support changing it once all QQuickWindow,
+ // QQuickRenderControl, etc. instances are destroyed, before creating new
+ // ones. That's why it is possible to have this test run with multiple QRhi
+ // backends.
+ QQuickWindow::setSceneGraphBackend(api);
+
+ QScopedPointer<QQuickRenderControl> renderControl(new QQuickRenderControl);
+ QScopedPointer<QQuickWindow> quickWindow(new QQuickWindow(renderControl.data()));
+#if QT_CONFIG(vulkan)
+ if (api == QSGRendererInterface::VulkanRhi)
+ quickWindow->setVulkanInstance(&vulkanInstance);
+#endif
+
+ QScopedPointer<QQmlEngine> qmlEngine(new QQmlEngine);
+ QScopedPointer<QQmlComponent> qmlComponent(new QQmlComponent(qmlEngine.data(),
+ testFileUrl(QLatin1String("rect.qml"))));
+ QVERIFY(!qmlComponent->isLoading());
+ if (qmlComponent->isError()) {
+ for (const QQmlError &error : qmlComponent->errors())
+ qWarning() << error.url() << error.line() << error;
+ }
+ QVERIFY(!qmlComponent->isError());
+
+ QObject *rootObject = qmlComponent->create();
+ if (qmlComponent->isError()) {
+ for (const QQmlError &error : qmlComponent->errors())
+ qWarning() << error.url() << error.line() << error;
+ }
+ QVERIFY(!qmlComponent->isError());
+
+ QQuickItem *rootItem = qobject_cast<QQuickItem *>(rootObject);
+ QVERIFY(rootItem);
+ QCOMPARE(rootItem->size(), QSize(200, 200));
+
+ quickWindow->contentItem()->setSize(rootItem->size());
+ quickWindow->setGeometry(0, 0, rootItem->width(), rootItem->height());
+
+ rootItem->setParentItem(quickWindow->contentItem());
+
+ const bool initSuccess = renderControl->initialize();
+
+ // now we cannot just test for initSuccess; it is highly likely that a
+ // number of configurations will simply fail in a CI environment (Vulkan,
+ // Metal, ...) So the only reasonable choice is to skip if initialize()
+ // failed. The exception for now is OpenGL - that should (usually) work.
+ if (!initSuccess) {
+#if QT_CONFIG(opengl)
+ if (api != QSGRendererInterface::OpenGLRhi
+ || !QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::OpenGL))
+#endif
+ {
+ QSKIP("Could not initialize graphics, perhaps unsupported graphics API, skipping");
+ }
+ }
+
+ QVERIFY(initSuccess);
+
+ QCOMPARE(quickWindow->rendererInterface()->graphicsApi(), api);
+
+ // What comes now is technically cheating - as long as QRhi is not a public
+ // API this is not something applications can follow doing. However, it
+ // allows us to test out the pipeline without having to write 4 different
+ // native (Vulkan, Metal, D3D11, OpenGL) implementations of all what's below.
+
+ QQuickRenderControlPrivate *rd = QQuickRenderControlPrivate::get(renderControl.data());
+ QRhi *rhi = rd->rhi;
+ Q_ASSERT(rhi);
+
+ const QSize size = rootItem->size().toSize();
+ QScopedPointer<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8, size, 1,
+ QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
+ QVERIFY(tex->build());
+
+ // depth-stencil is mandatory with RHI, although strictly speaking the
+ // scenegraph could operate without one, but it has no means to figure out
+ // the lack of a ds buffer, so just be nice and provide one.
+ QScopedPointer<QRhiRenderBuffer> ds(rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, size, 1));
+ QVERIFY(ds->build());
+
+ QRhiTextureRenderTargetDescription rtDesc(QRhiColorAttachment(tex.data()));
+ rtDesc.setDepthStencilBuffer(ds.data());
+ QScopedPointer<QRhiTextureRenderTarget> texRt(rhi->newTextureRenderTarget(rtDesc));
+ QScopedPointer<QRhiRenderPassDescriptor> rp(texRt->newCompatibleRenderPassDescriptor());
+ texRt->setRenderPassDescriptor(rp.data());
+ QVERIFY(texRt->build());
+
+ // redirect Qt Quick rendering into our texture
+ quickWindow->setRenderTarget(QQuickRenderTarget::fromRhiRenderTarget(texRt.data()));
+
+ for (int frame = 0; frame < 100; ++frame) {
+ // have to process events, e.g. to get queued metacalls delivered
+ QCoreApplication::processEvents();
+
+ if (frame > 0) {
+ // Quick animations will now think that ANIM_ADVANCE_PER_FRAME milliseconds have passed,
+ // even though in reality we have a tight loop that generates frames unthrottled.
+ animDriver->advance();
+ }
+
+ renderControl->polishItems();
+
+ // kick off the next frame on the QRhi (this internally calls QRhi::beginOffscreenFrame())
+ renderControl->beginFrame();
+
+ renderControl->sync();
+ renderControl->render();
+
+ bool readCompleted = false;
+ QRhiReadbackResult readResult;
+ QImage result;
+ readResult.completed = [&readCompleted, &readResult, &result, &rhi] {
+ readCompleted = true;
+ QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
+ readResult.pixelSize.width(), readResult.pixelSize.height(),
+ QImage::Format_RGBA8888_Premultiplied);
+ if (rhi->isYUpInFramebuffer())
+ result = wrapperImage.mirrored();
+ else
+ result = wrapperImage.copy();
+ };
+ QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
+ readbackBatch->readBackTexture(tex.data(), &readResult);
+ rd->cb->resourceUpdate(readbackBatch);
+
+ // our frame is done, submit
+ renderControl->endFrame();
+
+ // offscreen frames in QRhi are synchronous, meaning the readback has
+ // been finished at this point
+ QVERIFY(readCompleted);
+
+ QImage img = result;
+ QVERIFY(!img.isNull());
+ QCOMPARE(img.size(), rootItem->size());
+
+ const int maxFuzz = 2;
+
+ // The scene is: background, rectangle, text
+ // where rectangle rotates
+
+ QRgb background = img.pixel(5, 5);
+ QVERIFY(qAbs(qRed(background) - 70) < maxFuzz);
+ QVERIFY(qAbs(qGreen(background) - 130) < maxFuzz);
+ QVERIFY(qAbs(qBlue(background) - 180) < maxFuzz);
+
+ background = img.pixel(195, 195);
+ QVERIFY(qAbs(qRed(background) - 70) < maxFuzz);
+ QVERIFY(qAbs(qGreen(background) - 130) < maxFuzz);
+ QVERIFY(qAbs(qBlue(background) - 180) < maxFuzz);
+
+ // after about 1.25 seconds (animation time, one iteration is 16 ms
+ // thanks to our custom animation driver) the rectangle reaches a 90
+ // degree rotation, that should be frame 76
+ if (frame <= 2 || (frame >= 76 && frame <= 80)) {
+ QRgb c = img.pixel(28, 28); // rectangle
+ QVERIFY(qAbs(qRed(c) - 152) < maxFuzz);
+ QVERIFY(qAbs(qGreen(c) - 251) < maxFuzz);
+ QVERIFY(qAbs(qBlue(c) - 152) < maxFuzz);
+ } else {
+ QRgb c = img.pixel(28, 28); // background because rectangle got rotated so this pixel is not covered by it
+ QVERIFY(qAbs(qRed(c) - 70) < maxFuzz);
+ QVERIFY(qAbs(qGreen(c) - 130) < maxFuzz);
+ QVERIFY(qAbs(qBlue(c) - 180) < maxFuzz);
+ }
+ }
+}
+
+void tst_RenderControl::renderAndReadBackWithVulkanNative()
+{
+#if QT_CONFIG(vulkan)
+ if (!vulkanInstance.isValid())
+ QSKIP("Skipping native Vulkan test due to failing to create a VkInstance");
+
+ QQuickWindow::setSceneGraphBackend(QSGRendererInterface::VulkanRhi);
+
+ // We will create our own VkDevice and friends, which will then get used by
+ // Qt Quick as well (instead of creating its own objects), so this is a test
+ // of a typical "integrate Qt Quick content into an external (Vulkan-based)
+ // rendering engine" case.
+
+ QVulkanFunctions *f = vulkanInstance.functions();
+
+ uint32_t physDevCount = 0;
+ f->vkEnumeratePhysicalDevices(vulkanInstance.vkInstance(), &physDevCount, nullptr);
+ if (!physDevCount)
+ QSKIP("No Vulkan physical devices");
+
+ QVarLengthArray<VkPhysicalDevice, 4> physDevs(physDevCount);
+ VkResult err = f->vkEnumeratePhysicalDevices(vulkanInstance.vkInstance(), &physDevCount, physDevs.data());
+ QVERIFY(err == VK_SUCCESS);
+ QVERIFY(physDevCount);
+
+ // Just use the first physical device for now.
+ VkPhysicalDevice physDev = physDevs[0];
+
+ uint32_t queueCount = 0;
+ f->vkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, nullptr);
+ QVarLengthArray<VkQueueFamilyProperties, 4> queueFamilyProps(queueCount);
+ f->vkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, queueFamilyProps.data());
+
+ int gfxQueueFamilyIdx = -1;
+ for (int i = 0; i < queueFamilyProps.count(); ++i) {
+ if (queueFamilyProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
+ gfxQueueFamilyIdx = i;
+ break;
+ }
+ }
+ QVERIFY(gfxQueueFamilyIdx >= 0);
+
+ VkDeviceQueueCreateInfo queueInfo[2] = {};
+ const float prio[] = { 0 };
+ queueInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ queueInfo[0].queueFamilyIndex = uint32_t(gfxQueueFamilyIdx);
+ queueInfo[0].queueCount = 1;
+ queueInfo[0].pQueuePriorities = prio;
+
+ VkDevice dev = VK_NULL_HANDLE;
+ VkDeviceCreateInfo devInfo = {};
+ devInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+ devInfo.queueCreateInfoCount = 1;
+ devInfo.pQueueCreateInfos = queueInfo;
+ err = f->vkCreateDevice(physDev, &devInfo, nullptr, &dev);
+ if (err != VK_SUCCESS || !dev)
+ QSKIP("Skipping Vulkan test due to failing to create VkDevice");
+
+ QVulkanDeviceFunctions *df = vulkanInstance.deviceFunctions(dev);
+ QVERIFY(df);
+
+ {
+ QScopedPointer<QQuickRenderControl> renderControl(new QQuickRenderControl);
+ QScopedPointer<QQuickWindow> quickWindow(new QQuickWindow(renderControl.data()));
+ quickWindow->setVulkanInstance(&vulkanInstance);
+
+ QScopedPointer<QQmlEngine> qmlEngine(new QQmlEngine);
+ QScopedPointer<QQmlComponent> qmlComponent(new QQmlComponent(qmlEngine.data(),
+ testFileUrl(QLatin1String("rect.qml"))));
+ QVERIFY(!qmlComponent->isLoading());
+ if (qmlComponent->isError()) {
+ for (const QQmlError &error : qmlComponent->errors())
+ qWarning() << error.url() << error.line() << error;
+ }
+ QVERIFY(!qmlComponent->isError());
+
+ QObject *rootObject = qmlComponent->create();
+ if (qmlComponent->isError()) {
+ for (const QQmlError &error : qmlComponent->errors())
+ qWarning() << error.url() << error.line() << error;
+ }
+ QVERIFY(!qmlComponent->isError());
+
+ QQuickItem *rootItem = qobject_cast<QQuickItem *>(rootObject);
+ QVERIFY(rootItem);
+ QCOMPARE(rootItem->size(), QSize(200, 200));
+
+ // avoid trouble with the image - buffer copy later on
+ QVERIFY(int(rootItem->width()) % 4 == 0);
+ QVERIFY(int(rootItem->height()) % 4 == 0);
+
+ quickWindow->contentItem()->setSize(rootItem->size());
+ quickWindow->setGeometry(0, 0, rootItem->width(), rootItem->height());
+
+ rootItem->setParentItem(quickWindow->contentItem());
+
+ // Let Qt Quick and the underlying QRhi "adopt" our VkDevice, which
+ // will conveniently mean resource handles (buffers, images) are valid
+ // both there and here in our native Vulkan code as we all use the same
+ // device.
+ quickWindow->setGraphicsDevice(QQuickGraphicsDevice::fromDeviceObjects(physDev, dev, gfxQueueFamilyIdx));
+
+ const bool initSuccess = renderControl->initialize();
+ QVERIFY(initSuccess);
+ QCOMPARE(quickWindow->rendererInterface()->graphicsApi(), QSGRendererInterface::VulkanRhi);
+
+ // Will need a command pool/buffer to do the readback.
+ VkCommandPool cmdPool = VK_NULL_HANDLE;
+ VkCommandPoolCreateInfo poolInfo = {};
+ poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+ poolInfo.queueFamilyIndex = uint32_t(gfxQueueFamilyIdx);
+ VkResult err = df->vkCreateCommandPool(dev, &poolInfo, nullptr, &cmdPool);
+ QCOMPARE(err, VK_SUCCESS);
+
+ // Get a command queue, this is the same as what Qt Quick (QRhi) uses.
+ VkQueue cmdQueue = VK_NULL_HANDLE;
+ df->vkGetDeviceQueue(dev, uint32_t(gfxQueueFamilyIdx), 0, &cmdQueue);
+
+ // Do some sanity checks
+ QCOMPARE(physDev, *reinterpret_cast<VkPhysicalDevice *>(quickWindow->rendererInterface()->getResource(
+ quickWindow.data(), QSGRendererInterface::PhysicalDeviceResource)));
+ QCOMPARE(dev, *reinterpret_cast<VkDevice *>(quickWindow->rendererInterface()->getResource(
+ quickWindow.data(), QSGRendererInterface::DeviceResource)));
+ QCOMPARE(cmdQueue, *reinterpret_cast<VkQueue *>(quickWindow->rendererInterface()->getResource(
+ quickWindow.data(), QSGRendererInterface::CommandQueueResource)));
+
+ // Create the VkImage into which Qt Quick should render its contents.
+
+ VkImage img = VK_NULL_HANDLE;
+ VkImageCreateInfo imgInfo = {};
+ imgInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+ imgInfo.imageType = VK_IMAGE_TYPE_2D;
+ imgInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
+ imgInfo.extent.width = uint32_t(rootItem->width());
+ imgInfo.extent.height = uint32_t(rootItem->height());
+ imgInfo.extent.depth = 1;
+ imgInfo.mipLevels = imgInfo.arrayLayers = 1;
+ imgInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+ imgInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+ imgInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+ imgInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
+
+ err = df->vkCreateImage(dev, &imgInfo, nullptr, &img);
+ QCOMPARE(err, VK_SUCCESS);
+
+ VkPhysicalDeviceMemoryProperties memProps;
+ f->vkGetPhysicalDeviceMemoryProperties(physDev, &memProps);
+
+ auto findMemTypeIndex = [&memProps](uint32_t wantedBits, const VkMemoryRequirements &memReqs) {
+ uint32_t memTypeIndex = 0;
+ for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) {
+ if (memReqs.memoryTypeBits & (1 << i)) {
+ if ((memProps.memoryTypes[i].propertyFlags & wantedBits) == wantedBits) {
+ memTypeIndex = i;
+ break;
+ }
+ }
+ }
+ return memTypeIndex;
+ };
+
+ VkMemoryRequirements memReq;
+ df->vkGetImageMemoryRequirements(dev, img, &memReq);
+
+ VkMemoryAllocateInfo memInfo = {};
+ memInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+ memInfo.allocationSize = memReq.size;
+ memInfo.memoryTypeIndex = findMemTypeIndex(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, memReq);
+
+ VkDeviceMemory imgMem = VK_NULL_HANDLE;
+ err = df->vkAllocateMemory(dev, &memInfo, nullptr, &imgMem);
+ QCOMPARE(err, VK_SUCCESS);
+
+ err = df->vkBindImageMemory(dev, img, imgMem, 0);
+ QCOMPARE(err, VK_SUCCESS);
+
+ // Tell Qt Quick to target our VkImage.
+ quickWindow->setRenderTarget(QQuickRenderTarget::fromNativeTexture({ &img, VK_IMAGE_LAYOUT_PREINITIALIZED },
+ rootItem->size().toSize()));
+
+ // Create a readback buffer.
+ VkBuffer buf = VK_NULL_HANDLE;
+ VkDeviceMemory bufMem = VK_NULL_HANDLE;
+ VkBufferCreateInfo bufInfo = {};
+ bufInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+ bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
+ const int bufSize = int(rootItem->width()) * int(rootItem->height()) * 4;
+ bufInfo.size = bufSize;
+
+ df->vkCreateBuffer(dev, &bufInfo, nullptr, &buf);
+ df->vkGetBufferMemoryRequirements(dev, buf, &memReq);
+ memInfo.allocationSize = memReq.size;
+ memInfo.memoryTypeIndex = findMemTypeIndex(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, memReq);
+ err = df->vkAllocateMemory(dev, &memInfo, nullptr, &bufMem);
+ QCOMPARE(err, VK_SUCCESS);
+ df->vkBindBufferMemory(dev, buf, bufMem, 0);
+
+ for (int frame = 0; frame < 100; ++frame) {
+ // have to process events, e.g. to get queued metacalls delivered
+ QCoreApplication::processEvents();
+
+ if (frame > 0) {
+ // Quick animations will now think that ANIM_ADVANCE_PER_FRAME milliseconds have passed,
+ // even though in reality we have a tight loop that generates frames unthrottled.
+ animDriver->advance();
+ }
+
+ renderControl->polishItems();
+
+ renderControl->beginFrame();
+ renderControl->sync();
+ renderControl->render();
+ renderControl->endFrame(); // submits the command buffer generated by Qt Quick to the command queue
+ // ...and, it also waits for completion. This is different from how an on-screen frame would behave,
+ // offscreen frames are always synchronous with QRhi. Which is very handy for us here.
+
+ // Now issue a readback.
+
+ VkCommandBuffer cb = VK_NULL_HANDLE;
+ VkCommandBufferAllocateInfo cmdBufInfo = {};
+ cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+ cmdBufInfo.commandPool = cmdPool;
+ cmdBufInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+ cmdBufInfo.commandBufferCount = 1;
+
+ VkResult err = df->vkAllocateCommandBuffers(dev, &cmdBufInfo, &cb);
+ QCOMPARE(err, VK_SUCCESS);
+
+ VkCommandBufferBeginInfo cmdBufBeginInfo = {};
+ cmdBufBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+
+ err = df->vkBeginCommandBuffer(cb, &cmdBufBeginInfo);
+ QCOMPARE(err, VK_SUCCESS);
+
+ // rendering into a VkImage with Qt Quick leaves it in COLOR_ATTACHMENT_OPTIMAL
+ VkImageMemoryBarrier barrier = {};
+ barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+ barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
+ barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
+ barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+ barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+ barrier.image = img;
+
+ df->vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ 0, 0, nullptr, 0, nullptr,
+ 1, &barrier);
+
+ VkBufferImageCopy copyDesc = {};
+ copyDesc.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ copyDesc.imageSubresource.layerCount = 1;
+ copyDesc.imageExtent.width = uint32_t(rootItem->width());
+ copyDesc.imageExtent.height = uint32_t(rootItem->height());
+ copyDesc.imageExtent.depth = 1;
+
+ df->vkCmdCopyImageToBuffer(cb, img, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buf, 1, &copyDesc);
+
+ // Must restore the previous layout since nothing is telling Qt
+ // here that the layout changed so it will expect it to still be in
+ // COLOR_ATTACHMENT_OPTIMAL in the next iteration.
+ barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+ barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+ barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+
+ df->vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ 0, 0, nullptr, 0, nullptr,
+ 1, &barrier);
+
+ err = df->vkEndCommandBuffer(cb);
+ QCOMPARE(err, VK_SUCCESS);
+
+ VkSubmitInfo submitInfo = {};
+ submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+ submitInfo.commandBufferCount = 1;
+ submitInfo.pCommandBuffers = &cb;
+ VkPipelineStageFlags psf = VK_PIPELINE_STAGE_TRANSFER_BIT;
+ submitInfo.pWaitDstStageMask = &psf;
+
+ err = df->vkQueueSubmit(cmdQueue, 1, &submitInfo, VK_NULL_HANDLE);
+ QCOMPARE(err, VK_SUCCESS);
+
+ // just block until the image-to-buffer-copy result is available
+ df->vkQueueWaitIdle(cmdQueue);
+
+ df->vkFreeCommandBuffers(dev, cmdPool, 1, &cb);
+
+ uchar *p = nullptr;
+ df->vkMapMemory(dev, bufMem, 0, bufSize, 0, reinterpret_cast<void **>(&p));
+ // create a wrapper QImage
+ QImage img(reinterpret_cast<const uchar *>(p), rootItem->width(), rootItem->height(), QImage::Format_RGBA8888_Premultiplied);
+
+ // and the usual verification:
+
+ const int maxFuzz = 2;
+
+ // The scene is: background, rectangle, text
+ // where rectangle rotates
+
+ QRgb background = img.pixel(5, 5);
+ QVERIFY(qAbs(qRed(background) - 70) < maxFuzz);
+ QVERIFY(qAbs(qGreen(background) - 130) < maxFuzz);
+ QVERIFY(qAbs(qBlue(background) - 180) < maxFuzz);
+
+ background = img.pixel(195, 195);
+ QVERIFY(qAbs(qRed(background) - 70) < maxFuzz);
+ QVERIFY(qAbs(qGreen(background) - 130) < maxFuzz);
+ QVERIFY(qAbs(qBlue(background) - 180) < maxFuzz);
+
+ // after about 1.25 seconds (animation time, one iteration is 16 ms
+ // thanks to our custom animation driver) the rectangle reaches a 90
+ // degree rotation, that should be frame 76
+ if (frame <= 2 || (frame >= 76 && frame <= 80)) {
+ QRgb c = img.pixel(28, 28); // rectangle
+ QVERIFY(qAbs(qRed(c) - 152) < maxFuzz);
+ QVERIFY(qAbs(qGreen(c) - 251) < maxFuzz);
+ QVERIFY(qAbs(qBlue(c) - 152) < maxFuzz);
+ } else {
+ QRgb c = img.pixel(28, 28); // background because rectangle got rotated so this pixel is not covered by it
+ QVERIFY(qAbs(qRed(c) - 70) < maxFuzz);
+ QVERIFY(qAbs(qGreen(c) - 130) < maxFuzz);
+ QVERIFY(qAbs(qBlue(c) - 180) < maxFuzz);
+ }
+
+ img = QImage();
+ df->vkUnmapMemory(dev, bufMem);
+ }
+
+ df->vkDestroyImage(dev, img, nullptr);
+ df->vkFreeMemory(dev, imgMem, nullptr);
+
+ df->vkDestroyBuffer(dev, buf, nullptr);
+ df->vkFreeMemory(dev, bufMem, nullptr);
+
+ df->vkDestroyCommandPool(dev, cmdPool, nullptr);
+ }
+
+ // now that everything is destroyed, get rid of the VkDevice too
+ vulkanInstance.resetDeviceFunctions(dev);
+ df->vkDestroyDevice(dev, nullptr);
+
+#else
+ QSKIP("No Vulkan support in Qt build, skipping native Vulkan test");
+#endif
+}
+
#include "tst_qquickrendercontrol.moc"
QTEST_MAIN(tst_RenderControl)