aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLaszlo Agocs <[email protected]>2023-05-16 15:40:24 +0200
committerLaszlo Agocs <[email protected]>2023-05-22 21:33:36 +0200
commitad105709e9a648979bebff3009c416670903afd7 (patch)
treedcbfba5bb307efe40774c81f1fe420bac8efe8f5
parentd5511fdea6caa4dee7ea97314e64163d309bd884 (diff)
Add rhiunderqml example
And fix up the scenegraph example list on the Qt Quick example page. Task-number: QTBUG-113331 Change-Id: Ic7b97871253d16b778f7da6088f4a9130bef2ec5 Reviewed-by: Andy Nichols <[email protected]>
-rw-r--r--examples/quick/scenegraph/CMakeLists.txt1
-rw-r--r--examples/quick/scenegraph/rhiunderqml/CMakeLists.txt58
-rw-r--r--examples/quick/scenegraph/rhiunderqml/doc/images/rhiunderqml-example.jpgbin0 -> 35984 bytes
-rw-r--r--examples/quick/scenegraph/rhiunderqml/doc/src/rhiunderqml.qdoc218
-rw-r--r--examples/quick/scenegraph/rhiunderqml/main.cpp17
-rw-r--r--examples/quick/scenegraph/rhiunderqml/main.qml39
-rw-r--r--examples/quick/scenegraph/rhiunderqml/prebuilts_for_qmake/squircle_rhi.frag.qsbbin0 -> 1537 bytes
-rw-r--r--examples/quick/scenegraph/rhiunderqml/prebuilts_for_qmake/squircle_rhi.vert.qsbbin0 -> 1214 bytes
-rw-r--r--examples/quick/scenegraph/rhiunderqml/rhisquircle.cpp224
-rw-r--r--examples/quick/scenegraph/rhiunderqml/rhisquircle.h43
-rw-r--r--examples/quick/scenegraph/rhiunderqml/rhiunderqml.pro11
-rw-r--r--examples/quick/scenegraph/rhiunderqml/rhiunderqml.qrc7
-rw-r--r--examples/quick/scenegraph/scenegraph.pro3
-rw-r--r--examples/quick/scenegraph/shared/squircle_rhi.frag11
-rw-r--r--examples/quick/scenegraph/shared/squircle_rhi.vert7
-rw-r--r--src/quick/doc/src/examples.qdoc23
16 files changed, 648 insertions, 14 deletions
diff --git a/examples/quick/scenegraph/CMakeLists.txt b/examples/quick/scenegraph/CMakeLists.txt
index 834267b46e..74b35f9086 100644
--- a/examples/quick/scenegraph/CMakeLists.txt
+++ b/examples/quick/scenegraph/CMakeLists.txt
@@ -7,6 +7,7 @@ qt_internal_add_example(graph)
qt_internal_add_example(threadedanimation)
qt_internal_add_example(twotextureproviders)
qt_internal_add_example(customrendernode)
+qt_internal_add_example(rhiunderqml)
if(QT_FEATURE_opengl OR QT_FEATURE_opengles2 OR QT_FEATURE_opengles3)
qt_internal_add_example(fboitem)
qt_internal_add_example(openglunderqml)
diff --git a/examples/quick/scenegraph/rhiunderqml/CMakeLists.txt b/examples/quick/scenegraph/rhiunderqml/CMakeLists.txt
new file mode 100644
index 0000000000..90787084f9
--- /dev/null
+++ b/examples/quick/scenegraph/rhiunderqml/CMakeLists.txt
@@ -0,0 +1,58 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(rhiunderqml LANGUAGES CXX)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/quick/scenegraph/rhiunderqml")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick ShaderTools)
+
+qt_standard_project_setup()
+
+qt_add_executable(rhiunderqml WIN32 MACOSX_BUNDLE
+ main.cpp
+ rhisquircle.cpp rhisquircle.h
+)
+
+set_target_properties(rhiunderqml PROPERTIES
+ # Prevent name clash with build subdirectory on case-insensitive file systems
+ OUTPUT_NAME rhiunderqmlapp
+)
+
+target_link_libraries(rhiunderqml PRIVATE
+ Qt6::Core
+ Qt6::GuiPrivate
+ Qt6::Qml
+ Qt6::Quick
+)
+
+qt_add_qml_module(rhiunderqml
+ URI RhiUnderQML
+ QML_FILES
+ main.qml
+ RESOURCE_PREFIX /scenegraph/rhiunderqml
+ NO_RESOURCE_TARGET_PATH
+)
+
+qt_add_shaders(rhiunderqml "rhiunderqml_shaders"
+ PRECOMPILE
+ OPTIMIZED
+ PREFIX
+ /scenegraph/rhiunderqml
+ BASE
+ ../shared
+ FILES
+ ../shared/squircle_rhi.vert
+ ../shared/squircle_rhi.frag
+)
+
+install(TARGETS rhiunderqml
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/quick/scenegraph/rhiunderqml/doc/images/rhiunderqml-example.jpg b/examples/quick/scenegraph/rhiunderqml/doc/images/rhiunderqml-example.jpg
new file mode 100644
index 0000000000..e9c5bde3f0
--- /dev/null
+++ b/examples/quick/scenegraph/rhiunderqml/doc/images/rhiunderqml-example.jpg
Binary files differ
diff --git a/examples/quick/scenegraph/rhiunderqml/doc/src/rhiunderqml.qdoc b/examples/quick/scenegraph/rhiunderqml/doc/src/rhiunderqml.qdoc
new file mode 100644
index 0000000000..5eab97b490
--- /dev/null
+++ b/examples/quick/scenegraph/rhiunderqml/doc/src/rhiunderqml.qdoc
@@ -0,0 +1,218 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \example scenegraph/rhiunderqml
+ \title Scene Graph - RHI Under QML
+ \ingroup qtquickexamples
+ \brief Shows how to render directly with \l QRhi under a Qt Quick scene.
+
+ \image rhiunderqml-example.jpg
+
+ \section1 Introduction
+
+ The RHI Under QML example shows how an application can make use of the \l
+ QQuickWindow::beforeRendering() and \l
+ QQuickWindow::beforeRenderPassRecording() signals to draw custom \l{QRhi}-based
+ content under a Qt Quick scene.
+
+ Applications that wish to render \l QRhi content on top of the Qt Quick scene,
+ can do so by connecting to the \l QQuickWindow::afterRendering() and \l
+ QQuickWindow::afterRenderPassRecording() signals.
+
+ In this example, we will also see how it is possible to have values that
+ are exposed to QML which affect the QRhi-based rendering. We animate the
+ threshold value using a NumberAnimation in the QML file and this float
+ value is then passed on in a uniform buffer to the fragment shader.
+
+ The example is equivalent in most ways to the \l{Scene Graph - OpenGL Under
+ QML}{OpenGL Under QML}, \l{Scene Graph - Direct3D 11 Under QML}{Direct3D 11
+ Under QML}, \l{Scene Graph - Metal Under QML}{Metal Under QML}, and \l{Scene
+ Graph - Vulkan Under QML}{Vulkan Under QML} examples. Those examples render
+ the same content by directly using a 3D API. This example on the other hand
+ is fully cross-platform and portable, as it inherently supports operating
+ with all the 3D APIs supported by QRhi (such as, OpenGL, Vulkan, Metal,
+ Direct 3D 11 and 12).
+
+ \section1 Core Concepts
+
+ The beforeRendering() signal is emitted at the start of every frame, before
+ the scene graph starts its rendering, thus any \l QRhi draw calls that are
+ made as a response to this signal, will stack under the Qt Quick items.
+ However, there are two signals that are relevant here: the application's own
+ \l QRhi commands should be recorded onto the same command buffer that is
+ used by the scene graph, and what's more, the commands should belong to the
+ same render pass. beforeRendering() on its own is not sufficient for this
+ because it gets emitted at the start of the frame, before starting to record
+ a render pass via \l QRhiCommandBuffer::beginPass(). By also connecting to
+ beforeRenderPassRecording(), the application's own commands and the scene
+ graph's own rendering will end up in the right order:
+
+ \list
+ \li The scene graph's render loop calls \l QRhi::beginFrame()
+ \li \l QQuickWindow::beforeRendering() is emitted - the application prepares resources for its custom rendering
+ \li The scene graph calls \l QRhi::beginPass()
+ \li \l QQuickWindow::beforeRenderPassRecording() is emitted - the application records draw calls
+ \li The scene graph records draw calls
+ \endlist
+
+ \section1 Walkthrough
+
+ The custom rendering is encapsulated within a custom QQuickItem. \c
+ RhiSquircle derives from \l QQuickItem, and is exposed to QML (note the
+ \c{QML_ELEMENT}). The QML scene instantiates \c RhiSquircle. Note however
+ that this is not a visual item: the \l QQuickItem::ItemHasContents flag is
+ not set. Thus the item's position and size has no relevance and it does not
+ reimplement \l{QQuickItem::updatePaintNode()}{updatePaintNode()}.
+
+ \snippet scenegraph/rhiunderqml/rhisquircle.h 0
+
+ Instead, when the item gets associated with a \l QQuickWindow, it connects
+ to the \l{QQuickWindow::beforeSynchronizing()} signal. Using
+ Qt::DirectConnection is important since this signal is emitted on the Qt
+ Quick render thread, if there is one. We want the connected slot to be
+ invoked on this same thread.
+
+ \snippet scenegraph/rhiunderqml/rhisquircle.cpp init
+
+ In the scene graph's synchronizing phase, the rendering infrastructure is
+ created, if not yet done, and the data relevant for rendering is
+ synchronized, i.e. copied from the \c RhiSquircle item, that lives on the
+ main thread, to the \c SquircleRenderer object that lives on the render
+ thread. (if there is no render thread, then both objects live on the main
+ thread) Accessing data is safe because the main thread is blocked while the
+ render thread is executing its synchronize phase. See \l{Qt Quick Scene
+ Graph} for more information on the scene graph threading and rendering
+ model.
+
+ In addition to the value of \c t, the associated QQuickWindow pointer is
+ copied as well. While the \c SquircleRenderer could query
+ \l{QQuickItem::window()}{window()} on the \c RhiSquircle item even when
+ operating on the render thread, that is, in theory, not entirely safe. Hence
+ making a copy.
+
+ When setting up the \c SquircleRenderer, connections to the
+ \l{QQuickWindow::beforeRendering()}{beforeRendering()} and
+ \l{QQuickWindow::beforeRenderPassRecording()}{beforeRenderPassRecording()}
+ are made, which are the key to be able to act and inject the application's
+ custom 3D rendering commands at the appropriate time.
+
+ \snippet scenegraph/rhiunderqml/rhisquircle.cpp sync
+
+ When \l{QQuickWindow::beforeRendering()}{beforeRendering()} is emitted, the
+ QRhi resources needed for our custom rendering, such as \l QRhiBuffer, \l
+ QRhiGraphicsPipeline, and related objects, are created if not yet done.
+
+ The data in the buffers is updated (more precisely, the data update
+ operations are enqueued) using \l QRhiResourceUpdateBatch and \l
+ QRhiCommandBuffer::resourceUpdate(). The vertex buffer does not change its
+ contents once the initial set of vertices are uploaded to it. The uniform
+ buffer however is a \l{QRhiBuffer::Dynamic}{dynamic} buffer, as is typical
+ for such buffers. Its content, some regions at least, is updated for every
+ frame. Hence the unconditional call to
+ \l{QRhiResourceUpdateBatch::updateDynamicBuffer()}{updateDynamicBuffer()}
+ for offset 0 and a byte size of 4 (which is \c{sizeof(float)} since the C++
+ \c float type happens to match GLSL's 32-bit \c float). What is stored at
+ that position is the value of \c t, and that is updated in every frame,
+ meaning in every invocation of frameStart().
+
+ There is an additional float value in the buffer, starting at offset 4. This
+ is used to cater to the coordinate system differences of the 3D APIs: when
+ \l{QRhi::isYUpInNDC()}{isYUpInNDC()} returns \c false, which is the case
+ with Vulkan in particular, the value is set to -1.0 which leads to flipping
+ the Y value in the 2 component vector that is passed on (with interpolation)
+ to the fragment shader based on which the color is calculated. This way the
+ output on the screen is identical (i.e. the top-left corner is green-ish,
+ the bottom-left is red-ish), regardless of which 3D API is in use. This
+ value is updated only once in the uniform buffer, similarly to the vertex
+ buffer. This highlights an issue low-level rendering code that aims to be
+ portable often needs to deal with: the coordinate system differences in
+ normalized device coordinates (NDC) and in images and framebuffers. For
+ example, the NDC uses a origin-at-bottom-left system everywhere except
+ Vulkan. Whereas framebuffers use an origin-at-top-left system everywhere
+ except OpenGL. Typical renderers that work with a perspective projection can
+ often be oblivious to this problem by conveniently relying on
+ \l{QRhi::clipSpaceCorrMatrix()}, which is a matrix that can be multiplied in
+ to the projection matrix, and applies both an Y flip when needed, and also
+ caters to the fact that clip space depth runs \c{-1..1} with OpenGL but
+ \c{0..1} everywhere else. However, in some cases, such as in this example,
+ this is not applicable. Rather, the application and shader logic needs to
+ perform the necessary adjustment of vertex and UV positions as appropriate
+ based on querying \l QRhi::isYUpInNDC() and \l QRhi::isYUpInFramebuffer().
+
+ To gain access to the \l QRhi and \l QRhiSwapChain objects Qt Quick uses,
+ they can simply be queried from the \l QQuickWindow. Note that this assumes
+ that the QQuickWindow is a regular, on-screen window. If it used \l
+ QQuickRenderControl instead, e.g. to perform off-screen rendering into a
+ texture, querying the swapchain would be wrong since there is no swapchain
+ then.
+
+ Due to the signal being emitted after Qt Quick calls \l QRhi::beginFrame(),
+ it is already possible to query the command buffer and render target from
+ the swapchain. This is what allows to conveniently issue a \l
+ QRhiCommandBuffer::resourceUpdate() on the object returned from \l
+ QRhiSwapChain::currentFrameCommandBuffer(). When creating a graphics
+ pipeline, a QRhiRenderPassDescriptor can be retrieved from the
+ QRhiRenderTarget returned from \l QRhiSwapChain::currentFrameRenderTarget().
+ (note that this means the graphics pipeline built here is suitable only for
+ rendering to the swapchain, or at best another render target that is
+ \l{QRhiRenderPassDescriptor::isCompatible()}{compatible} with it; it is
+ likely that if we wanted to render to a texture, then a different
+ QRhiRenderPassDescriptor, and so a different graphics pipeline, would be
+ needed since the texture and swapchain formats may differ)
+
+ \snippet scenegraph/rhiunderqml/rhisquircle.cpp frame-start
+
+ Finally, upon \l QQuickWindow::beforeRenderPassRecording(), a draw call for
+ a triangle strip with 4 vertices is recorded. This example simply draws a
+ quad in practice, and calculates the pixel colors using the logic in the
+ fragment shaders, but applications are free to do more complicated drawing:
+ creating multiple graphics pipelines and recording multiple draw calls is
+ perfectly fine as well. The important thing to keep in mind is that whatever
+ is recorded on the \l QRhiCommandBuffer retrieved from the window's
+ \l{QRhiSwapChain}{swapchain}, it is effectively prepended before the Qt Quick
+ scene graph's own rendering within the main render pass. This means that,
+ for example, if depth buffer usage with depth testing and writing out depth
+ values is involved, then the Qt Quick content may be affected by the values
+ written to the depth buffer. See \l{Qt Quick Scene Graph Default Renderer}
+ for details on the scene graph's renderer, in particular the sections about
+ the handling of \e opaque and \e{alpha blended} primitives.
+
+ To get the window size in pixels, \l QRhiRenderTarget::pixelSize() is used.
+ This is convenient because this way the example does not need to calculate
+ the viewport size by other means and does not have to worry about applying
+ the \l{QWindow::devicePixelRatio()}{high DPI scale factor}, if there is any.
+
+ \snippet scenegraph/rhiunderqml/rhisquircle.cpp frame-render
+
+ The vertex and fragment shaders go through the standard QRhi shader
+ conditioning pipeline. Initially written as Vulkan-compatible GLSL, they get
+ compiled to SPIR-V and then transpiled to other shading languages by Qt's
+ tools. When using CMake, the example relies on the \c qt_add_shaders command
+ that makes it simple and convenient to bundle the shaders with the
+ application and perform the necessary processing at build time. See \l{Qt
+ Shader Tools Build System Integration} for details.
+
+ Specifying \c BASE helps removing the \c{../shared} prefix, while \c PREFIX
+ adds the intended \c{/scenegraph/rhiunderqml} prefix. Thus the final path is
+ \c{:/scenegraph/rhiunderqml/squircle_rhi.vert.qsb}.
+
+ \badcode
+ qt_add_shaders(rhiunderqml "rhiunderqml_shaders"
+ PRECOMPILE
+ OPTIMIZED
+ PREFIX
+ /scenegraph/rhiunderqml
+ BASE
+ ../shared
+ FILES
+ ../shared/squircle_rhi.vert
+ ../shared/squircle_rhi.frag
+ )
+ \endcode
+
+ To support qmake, the example still ships the \c{.qsb} files that would
+ normally be generated at build time, and lists them in the qrc file. This
+ approach is however not recommended for new applications that use CMake as
+ the build system.
+ */
diff --git a/examples/quick/scenegraph/rhiunderqml/main.cpp b/examples/quick/scenegraph/rhiunderqml/main.cpp
new file mode 100644
index 0000000000..51b8d18ddb
--- /dev/null
+++ b/examples/quick/scenegraph/rhiunderqml/main.cpp
@@ -0,0 +1,17 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QGuiApplication>
+#include <QtQuick/QQuickView>
+
+int main(int argc, char **argv)
+{
+ QGuiApplication app(argc, argv);
+
+ QQuickView view;
+ view.setResizeMode(QQuickView::SizeRootObjectToView);
+ view.setSource(QUrl("qrc:///scenegraph/rhiunderqml/main.qml"));
+ view.show();
+
+ return app.exec();
+}
diff --git a/examples/quick/scenegraph/rhiunderqml/main.qml b/examples/quick/scenegraph/rhiunderqml/main.qml
new file mode 100644
index 0000000000..3faa028457
--- /dev/null
+++ b/examples/quick/scenegraph/rhiunderqml/main.qml
@@ -0,0 +1,39 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import RhiUnderQML
+
+Item {
+ width: 320
+ height: 480
+
+ RhiSquircle {
+ SequentialAnimation on t {
+ NumberAnimation { to: 1; duration: 2500; easing.type: Easing.InQuad }
+ NumberAnimation { to: 0; duration: 2500; easing.type: Easing.OutQuad }
+ loops: Animation.Infinite
+ running: true
+ }
+ }
+
+ Rectangle {
+ color: Qt.rgba(1, 1, 1, 0.7)
+ radius: 10
+ border.width: 1
+ border.color: "white"
+ anchors.fill: label
+ anchors.margins: -10
+ }
+
+ Text {
+ id: label
+ color: "black"
+ wrapMode: Text.WordWrap
+ text: qsTr("The background here is a squircle rendered with QRhi using the beforeRendering() and beforeRenderPassRecording() signals in QQuickWindow. This text label and its border is rendered using QML")
+ anchors.right: parent.right
+ anchors.left: parent.left
+ anchors.bottom: parent.bottom
+ anchors.margins: 20
+ }
+}
diff --git a/examples/quick/scenegraph/rhiunderqml/prebuilts_for_qmake/squircle_rhi.frag.qsb b/examples/quick/scenegraph/rhiunderqml/prebuilts_for_qmake/squircle_rhi.frag.qsb
new file mode 100644
index 0000000000..077a31f945
--- /dev/null
+++ b/examples/quick/scenegraph/rhiunderqml/prebuilts_for_qmake/squircle_rhi.frag.qsb
Binary files differ
diff --git a/examples/quick/scenegraph/rhiunderqml/prebuilts_for_qmake/squircle_rhi.vert.qsb b/examples/quick/scenegraph/rhiunderqml/prebuilts_for_qmake/squircle_rhi.vert.qsb
new file mode 100644
index 0000000000..1e87cf09a7
--- /dev/null
+++ b/examples/quick/scenegraph/rhiunderqml/prebuilts_for_qmake/squircle_rhi.vert.qsb
Binary files differ
diff --git a/examples/quick/scenegraph/rhiunderqml/rhisquircle.cpp b/examples/quick/scenegraph/rhiunderqml/rhisquircle.cpp
new file mode 100644
index 0000000000..135cf128e3
--- /dev/null
+++ b/examples/quick/scenegraph/rhiunderqml/rhisquircle.cpp
@@ -0,0 +1,224 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "rhisquircle.h"
+#include <QtQuick/QQuickWindow>
+#include <QtCore/QFile>
+#include <QtCore/QRunnable>
+
+#include <rhi/qrhi.h>
+
+class SquircleRenderer : public QObject
+{
+ Q_OBJECT
+public:
+ void setT(qreal t) { m_t = t; }
+ void setWindow(QQuickWindow *window) { m_window = window; }
+
+public slots:
+ void frameStart();
+ void mainPassRecordingStart();
+
+private:
+ qreal m_t = 0;
+ QQuickWindow *m_window;
+ QShader m_vertexShader;
+ QShader m_fragmentShader;
+ std::unique_ptr<QRhiBuffer> m_vertexBuffer;
+ std::unique_ptr<QRhiBuffer> m_uniformBuffer;
+ std::unique_ptr<QRhiShaderResourceBindings> m_srb;
+ std::unique_ptr<QRhiGraphicsPipeline> m_pipeline;
+};
+
+//! [init]
+RhiSquircle::RhiSquircle()
+{
+ connect(this, &QQuickItem::windowChanged, this, &RhiSquircle::handleWindowChanged);
+}
+
+void RhiSquircle::handleWindowChanged(QQuickWindow *win)
+{
+ if (win) {
+ connect(win, &QQuickWindow::beforeSynchronizing, this, &RhiSquircle::sync, Qt::DirectConnection);
+ connect(win, &QQuickWindow::sceneGraphInvalidated, this, &RhiSquircle::cleanup, Qt::DirectConnection);
+ // Ensure we start with cleared to black. The squircle's blend mode relies on this.
+ win->setColor(Qt::black);
+ }
+}
+//! [init]
+
+// The safe way to release custom graphics resources is to both connect to
+// sceneGraphInvalidated() and implement releaseResources(). To support
+// threaded render loops the latter performs the SquircleRenderer destruction
+// via scheduleRenderJob(). Note that the RhiSquircle may be gone by the time
+// the QRunnable is invoked.
+
+void RhiSquircle::cleanup()
+{
+ // This function is invoked on the render thread, if there is one.
+
+ delete m_renderer;
+ m_renderer = nullptr;
+}
+
+class CleanupJob : public QRunnable
+{
+public:
+ CleanupJob(SquircleRenderer *renderer) : m_renderer(renderer) { }
+ void run() override { delete m_renderer; }
+private:
+ SquircleRenderer *m_renderer;
+};
+
+void RhiSquircle::releaseResources()
+{
+ window()->scheduleRenderJob(new CleanupJob(m_renderer), QQuickWindow::BeforeSynchronizingStage);
+ m_renderer = nullptr;
+}
+
+void RhiSquircle::setT(qreal t)
+{
+ if (t == m_t)
+ return;
+ m_t = t;
+ emit tChanged();
+ if (window())
+ window()->update();
+}
+
+//! [sync]
+void RhiSquircle::sync()
+{
+ // This function is invoked on the render thread, if there is one.
+
+ if (!m_renderer) {
+ m_renderer = new SquircleRenderer;
+ // Initializing resources is done before starting to record the
+ // renderpass, regardless of wanting an underlay or overlay.
+ connect(window(), &QQuickWindow::beforeRendering, m_renderer, &SquircleRenderer::frameStart, Qt::DirectConnection);
+ // Here we want an underlay and therefore connect to
+ // beforeRenderPassRecording. Changing to afterRenderPassRecording
+ // would render the squircle on top (overlay).
+ connect(window(), &QQuickWindow::beforeRenderPassRecording, m_renderer, &SquircleRenderer::mainPassRecordingStart, Qt::DirectConnection);
+ }
+ m_renderer->setT(m_t);
+ m_renderer->setWindow(window());
+}
+//! [sync]
+
+static QShader getShader(const QString &name)
+{
+ QFile f(name);
+ if (f.open(QIODevice::ReadOnly))
+ return QShader::fromSerialized(f.readAll());
+
+ return QShader();
+}
+
+static const float vertices[] = {
+ -1, -1,
+ 1, -1,
+ -1, 1,
+ 1, 1
+};
+
+//! [frame-start]
+void SquircleRenderer::frameStart()
+{
+ // This function is invoked on the render thread, if there is one.
+
+ QRhi *rhi = m_window->rhi();
+ if (!rhi) {
+ qWarning("QQuickWindow is not using QRhi for rendering");
+ return;
+ }
+ QRhiSwapChain *swapChain = m_window->swapChain();
+ if (!swapChain) {
+ qWarning("No QRhiSwapChain?");
+ return;
+ }
+ QRhiResourceUpdateBatch *resourceUpdates = rhi->nextResourceUpdateBatch();
+
+ if (!m_pipeline) {
+ m_vertexShader = getShader(QLatin1String(":/scenegraph/rhiunderqml/squircle_rhi.vert.qsb"));
+ if (!m_vertexShader.isValid())
+ qWarning("Failed to load vertex shader; rendering will be incorrect");
+
+ m_fragmentShader = getShader(QLatin1String(":/scenegraph/rhiunderqml/squircle_rhi.frag.qsb"));
+ if (!m_fragmentShader.isValid())
+ qWarning("Failed to load fragment shader; rendering will be incorrect");
+
+ m_vertexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices)));
+ m_vertexBuffer->create();
+ resourceUpdates->uploadStaticBuffer(m_vertexBuffer.get(), vertices);
+
+ const quint32 UBUF_SIZE = 4 + 4; // 2 floats
+ m_uniformBuffer.reset(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, UBUF_SIZE));
+ m_uniformBuffer->create();
+
+ float yDir = rhi->isYUpInNDC() ? 1.0f : -1.0f;
+ resourceUpdates->updateDynamicBuffer(m_uniformBuffer.get(), 4, 4, &yDir);
+
+ m_srb.reset(rhi->newShaderResourceBindings());
+ const auto visibleToAll = QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage;
+ m_srb->setBindings({
+ QRhiShaderResourceBinding::uniformBuffer(0, visibleToAll, m_uniformBuffer.get())
+ });
+ m_srb->create();
+
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({
+ { 2 * sizeof(float) }
+ });
+ inputLayout.setAttributes({
+ { 0, 0, QRhiVertexInputAttribute::Float2, 0 }
+ });
+
+ m_pipeline.reset(rhi->newGraphicsPipeline());
+ m_pipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip);
+ QRhiGraphicsPipeline::TargetBlend blend;
+ blend.enable = true;
+ blend.srcColor = QRhiGraphicsPipeline::SrcAlpha;
+ blend.srcAlpha = QRhiGraphicsPipeline::SrcAlpha;
+ blend.dstColor = QRhiGraphicsPipeline::One;
+ blend.dstAlpha = QRhiGraphicsPipeline::One;
+ m_pipeline->setTargetBlends({ blend });
+ m_pipeline->setShaderStages({
+ { QRhiShaderStage::Vertex, m_vertexShader },
+ { QRhiShaderStage::Fragment, m_fragmentShader }
+ });
+ m_pipeline->setVertexInputLayout(inputLayout);
+ m_pipeline->setShaderResourceBindings(m_srb.get());
+ m_pipeline->setRenderPassDescriptor(swapChain->currentFrameRenderTarget()->renderPassDescriptor());
+ m_pipeline->create();
+ }
+
+ float t = m_t;
+ resourceUpdates->updateDynamicBuffer(m_uniformBuffer.get(), 0, 4, &t);
+
+ swapChain->currentFrameCommandBuffer()->resourceUpdate(resourceUpdates);
+}
+//! [frame-start]
+
+//! [frame-render]
+void SquircleRenderer::mainPassRecordingStart()
+{
+ // This function is invoked on the render thread, if there is one.
+
+ QRhi *rhi = m_window->rhi();
+ QRhiSwapChain *swapChain = m_window->swapChain();
+ if (!rhi || !swapChain)
+ return;
+
+ const QSize outputPixelSize = swapChain->currentFrameRenderTarget()->pixelSize();
+ QRhiCommandBuffer *cb = m_window->swapChain()->currentFrameCommandBuffer();
+ cb->setViewport({ 0.0f, 0.0f, float(outputPixelSize.width()), float(outputPixelSize.height()) });
+ cb->setGraphicsPipeline(m_pipeline.get());
+ cb->setShaderResources();
+ const QRhiCommandBuffer::VertexInput vbufBinding(m_vertexBuffer.get(), 0);
+ cb->setVertexInput(0, 1, &vbufBinding);
+ cb->draw(4);
+}
+//! [frame-render]
+
+#include "rhisquircle.moc"
diff --git a/examples/quick/scenegraph/rhiunderqml/rhisquircle.h b/examples/quick/scenegraph/rhiunderqml/rhisquircle.h
new file mode 100644
index 0000000000..507e7b930f
--- /dev/null
+++ b/examples/quick/scenegraph/rhiunderqml/rhisquircle.h
@@ -0,0 +1,43 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef RHISQUIRCLE_H
+#define RHISQUIRCLE_H
+
+#include <QtQuick/QQuickItem>
+#include <QtQuick/QQuickWindow>
+
+class SquircleRenderer;
+
+//! [0]
+class RhiSquircle : public QQuickItem
+{
+ Q_OBJECT
+ Q_PROPERTY(qreal t READ t WRITE setT NOTIFY tChanged)
+ QML_ELEMENT
+
+public:
+ RhiSquircle();
+
+ qreal t() const { return m_t; }
+ void setT(qreal t);
+
+signals:
+ void tChanged();
+
+public slots:
+ void sync();
+ void cleanup();
+
+private slots:
+ void handleWindowChanged(QQuickWindow *win);
+
+private:
+ void releaseResources() override;
+
+ qreal m_t = 0;
+ SquircleRenderer *m_renderer = nullptr;
+};
+//! [0]
+
+#endif
diff --git a/examples/quick/scenegraph/rhiunderqml/rhiunderqml.pro b/examples/quick/scenegraph/rhiunderqml/rhiunderqml.pro
new file mode 100644
index 0000000000..af85005e80
--- /dev/null
+++ b/examples/quick/scenegraph/rhiunderqml/rhiunderqml.pro
@@ -0,0 +1,11 @@
+QT += gui-private qml quick
+CONFIG += qmltypes
+QML_IMPORT_NAME = RhiUnderQML
+QML_IMPORT_MAJOR_VERSION = 1
+
+HEADERS += rhisquircle.h
+SOURCES += rhisquircle.cpp main.cpp
+RESOURCES += rhiunderqml.qrc
+
+target.path = $$[QT_INSTALL_EXAMPLES]/quick/scenegraph/rhiunderqml
+INSTALLS += target
diff --git a/examples/quick/scenegraph/rhiunderqml/rhiunderqml.qrc b/examples/quick/scenegraph/rhiunderqml/rhiunderqml.qrc
new file mode 100644
index 0000000000..690cb36444
--- /dev/null
+++ b/examples/quick/scenegraph/rhiunderqml/rhiunderqml.qrc
@@ -0,0 +1,7 @@
+<RCC>
+ <qresource prefix="/scenegraph/rhiunderqml">
+ <file>main.qml</file>
+ <file alias="squircle_rhi.vert.qsb">prebuilts_for_qmake/squircle_rhi.vert.qsb</file>
+ <file alias="squircle_rhi.frag.qsb">prebuilts_for_qmake/squircle_rhi.frag.qsb</file>
+ </qresource>
+</RCC>
diff --git a/examples/quick/scenegraph/scenegraph.pro b/examples/quick/scenegraph/scenegraph.pro
index a438bb8341..85dd7bd439 100644
--- a/examples/quick/scenegraph/scenegraph.pro
+++ b/examples/quick/scenegraph/scenegraph.pro
@@ -11,7 +11,8 @@ SUBDIRS += \
custommaterial \
graph \
threadedanimation \
- twotextureproviders
+ twotextureproviders \
+ rhiunderqml
macos|ios {
SUBDIRS += \
diff --git a/examples/quick/scenegraph/shared/squircle_rhi.frag b/examples/quick/scenegraph/shared/squircle_rhi.frag
index 8da62b93e6..164b0ef404 100644
--- a/examples/quick/scenegraph/shared/squircle_rhi.frag
+++ b/examples/quick/scenegraph/shared/squircle_rhi.frag
@@ -5,12 +5,13 @@ layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
float t;
-} ubuf;
+ float y_dir;
+};
void main()
{
- float i = 1. - (pow(abs(coords.x), 4.) + pow(abs(coords.y), 4.));
- i = smoothstep(ubuf.t - 0.8, ubuf.t + 0.8, i);
- i = floor(i * 20.) / 20.;
- fragColor = vec4(coords * .5 + .5, i, i);
+ float i = 1.0 - (pow(abs(coords.x), 4.0) + pow(abs(coords.y), 4.0));
+ i = smoothstep(t - 0.8, t + 0.8, i);
+ i = floor(i * 20.0) / 20.0;
+ fragColor = vec4(coords * 0.5 + 0.5, i, i);
}
diff --git a/examples/quick/scenegraph/shared/squircle_rhi.vert b/examples/quick/scenegraph/shared/squircle_rhi.vert
index b57dfdfe10..bf079ab786 100644
--- a/examples/quick/scenegraph/shared/squircle_rhi.vert
+++ b/examples/quick/scenegraph/shared/squircle_rhi.vert
@@ -1,13 +1,16 @@
#version 440
layout(location = 0) in vec4 vertices;
-
layout(location = 0) out vec2 coords;
-out gl_PerVertex { vec4 gl_Position; };
+layout(std140, binding = 0) uniform buf {
+ float t;
+ float y_dir;
+};
void main()
{
gl_Position = vertices;
coords = vertices.xy;
+ coords.y *= y_dir;
}
diff --git a/src/quick/doc/src/examples.qdoc b/src/quick/doc/src/examples.qdoc
index 01ed822f2c..efb7956766 100644
--- a/src/quick/doc/src/examples.qdoc
+++ b/src/quick/doc/src/examples.qdoc
@@ -149,16 +149,27 @@ Creator.
\b{Scene Graph}
\list
\li \l{Scene Graph - Custom Material}{Custom Material}
+ \li \l{Scene Graph - RHI Under QML}{Portable QRhi-based 3D rendering as a scene underlay}
\li \l{Scene Graph - Two Texture Providers}{Texture Providers and Materials}
\li \l{Scene Graph - Custom Geometry}{Custom Geometry}
\li \l{Scene Graph - Graph}{Graph}
- \li \l{Scene Graph - OpenGL Under QML}{OpenGL Under QML}
- \li \l{Scene Graph - Direct3D 11 Under QML}{Direct3D 11 Under QML}
- \li \l{Scene Graph - Vulkan Under QML}{Vulkan Under QML}
- \li \l{Scene Graph - Vulkan Texture Import}{Vulkan Texture Import}
- \li \l{Scene Graph - Metal Under QML}{Metal Under QML}
- \li \l{Scene Graph - Metal Texture Import}{Metal Texture Import}
+ \endlist
+ \enddiv
+\enddiv
+
+\div {class="multi-column"}
+ \div {class="doc-column"}
+ \b{Extending the Scene Graph using native 3D APIs}
+ \list
+ \li \l{Scene Graph - Vulkan Under QML}{Vulkan-based 3D rendering as a scene underlay}
+ \li \l{Scene Graph - Vulkan Texture Import}{Implementing a custom QQuickItem that displays a native Vulkan image}
+ \li \l{Scene Graph - Metal Under QML}{Metal-based 3D rendering as a scene underlay}
+ \li \l{Scene Graph - Metal Texture Import}{Implementing a custom QQuickItem that displays a native Metal texture}
+ \li \l{Scene Graph - Direct3D 11 Under QML}{Direct3D 11-based rendering as a scene underlay}
+ \li \l{Scene Graph - OpenGL Under QML}{OpenGL-based rendering as a scene underlay}
\li \l{Scene Graph - Rendering FBOs}{Rendering to OpenGL FBOs}
+ \li \l{QQuickRenderControl OpenGL Example}{Redirecting Qt Quick rendering into an OpenGL texture}
+ \li \l{QQuickRenderControl D3D11 Example}{Redirecting Qt Quick rendering into a Direct 3D texture}
\endlist
\enddiv
\div {class="doc-column"}