diff options
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 Binary files differnew file mode 100644 index 0000000000..4d56a946cc --- /dev/null +++ b/examples/quick/rendercontrol/rendercontrol_d3d11/doc/images/rendercontrol-d3d11-example.jpg 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 Binary files differindex 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 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(¶ms); + } 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, ©Desc); + + // 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) |