aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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)