diff options
author | Laszlo Agocs <[email protected]> | 2023-05-16 15:40:24 +0200 |
---|---|---|
committer | Laszlo Agocs <[email protected]> | 2023-05-22 21:33:36 +0200 |
commit | ad105709e9a648979bebff3009c416670903afd7 (patch) | |
tree | dcbfba5bb307efe40774c81f1fe420bac8efe8f5 | |
parent | d5511fdea6caa4dee7ea97314e64163d309bd884 (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]>
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 Binary files differnew file mode 100644 index 0000000000..e9c5bde3f0 --- /dev/null +++ b/examples/quick/scenegraph/rhiunderqml/doc/images/rhiunderqml-example.jpg 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 Binary files differnew file mode 100644 index 0000000000..077a31f945 --- /dev/null +++ b/examples/quick/scenegraph/rhiunderqml/prebuilts_for_qmake/squircle_rhi.frag.qsb 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 Binary files differnew file mode 100644 index 0000000000..1e87cf09a7 --- /dev/null +++ b/examples/quick/scenegraph/rhiunderqml/prebuilts_for_qmake/squircle_rhi.vert.qsb 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"} |