diff options
author | Ben Fletcher <ben.fletcher@me.com> | 2022-10-16 10:59:29 -0700 |
---|---|---|
committer | Ben Fletcher <ben.fletcher@me.com> | 2022-11-01 10:39:02 -0700 |
commit | 7be8566719d359ba2696f771e4ac7d802383dd9d (patch) | |
tree | 05ac78f2276d7d37d53f1370953fcb99be1009eb /examples/quick | |
parent | a9900e9f30fa5f6c943a7c363d57c0d9d748b92f (diff) |
scenegraph: Add example for QSGRenderNode based RHI rendering
Add an example to demonstrate RHI rendering in the scenegraph with a
custom QSGRenderNode. Works for Vulkan/OpenGL/Metal/D3D rendering
directly, and into a layer.
Change-Id: I0333f63fd729312b71e51f5b6376e46f8afe1fe6
Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io>
Diffstat (limited to 'examples/quick')
10 files changed, 586 insertions, 0 deletions
diff --git a/examples/quick/scenegraph/CMakeLists.txt b/examples/quick/scenegraph/CMakeLists.txt index b4237ddf68..834267b46e 100644 --- a/examples/quick/scenegraph/CMakeLists.txt +++ b/examples/quick/scenegraph/CMakeLists.txt @@ -6,6 +6,7 @@ qt_internal_add_example(custommaterial) qt_internal_add_example(graph) qt_internal_add_example(threadedanimation) qt_internal_add_example(twotextureproviders) +qt_internal_add_example(customrendernode) 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/customrendernode/CMakeLists.txt b/examples/quick/scenegraph/customrendernode/CMakeLists.txt new file mode 100644 index 0000000000..0d6776e53a --- /dev/null +++ b/examples/quick/scenegraph/customrendernode/CMakeLists.txt @@ -0,0 +1,55 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(customrendernode LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/quick/scenegraph/customrendernode") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick ShaderTools) + +qt_add_executable(customrendernode + main.cpp + customrender.cpp customrender.h +) + +set_target_properties(customrendernode PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(customrendernode PUBLIC + Qt::Core + Qt::GuiPrivate + Qt::Qml + Qt::QuickPrivate +) + +qt_add_qml_module(customrendernode + URI SceneGraphRendering + VERSION 1.0 + QML_FILES + main.qml + RESOURCE_PREFIX /scenegraph/customrendernode + NO_RESOURCE_TARGET_PATH +) + +qt6_add_shaders(customrendernode "shaders" + PREFIX + "/scenegraph/customrendernode" + FILES + "shaders/customrender.vert" + "shaders/customrender.frag" +) + +install(TARGETS customrendernode + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/quick/scenegraph/customrendernode/customrender.cpp b/examples/quick/scenegraph/customrendernode/customrender.cpp new file mode 100644 index 0000000000..e95341438e --- /dev/null +++ b/examples/quick/scenegraph/customrendernode/customrender.cpp @@ -0,0 +1,245 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "customrender.h" +#include <QSGTextureProvider> +#include <QSGRenderNode> +#include <QSGTransformNode> +#include <QSGRendererInterface> +#include <QQuickWindow> +#include <QFile> +#include <private/qrhi_p.h> +#include <private/qsgrendernode_p.h> + +class CustomRenderNode : public QSGRenderNode +{ +public: + CustomRenderNode(QQuickWindow *window); + virtual ~CustomRenderNode(); + + void setVertices(const QList<QVector2D> &vertices); + + void prepare() override; + void render(const RenderState *state) override; + void releaseResources() override; + RenderingFlags flags() const override; + QSGRenderNode::StateFlags changedStates() const override; + +protected: + QQuickWindow *m_window = nullptr; + QRhiBuffer *m_vertexBuffer = nullptr; + QRhiBuffer *m_uniformBuffer = nullptr; + QRhiShaderResourceBindings *m_resourceBindings = nullptr; + QRhiGraphicsPipeline *m_pipeLine = nullptr; + QList<QRhiShaderStage> m_shaders; + bool m_verticesDirty = true; + QList<QVector2D> m_vertices; +}; + +CustomRenderNode::CustomRenderNode(QQuickWindow *window) : m_window(window) +{ + Q_ASSERT(QFile::exists(":/scenegraph/customrendernode/shaders/customrender.vert.qsb")); + Q_ASSERT(QFile::exists(":/scenegraph/customrendernode/shaders/customrender.frag.qsb")); + + QFile file; + file.setFileName(":/scenegraph/customrendernode/shaders/customrender.vert.qsb"); + file.open(QFile::ReadOnly); + m_shaders.append( + QRhiShaderStage(QRhiShaderStage::Vertex, QShader::fromSerialized(file.readAll()))); + + file.close(); + file.setFileName(":/scenegraph/customrendernode/shaders/customrender.frag.qsb"); + file.open(QFile::ReadOnly); + m_shaders.append( + QRhiShaderStage(QRhiShaderStage::Fragment, QShader::fromSerialized(file.readAll()))); +} + +CustomRenderNode::~CustomRenderNode() +{ + if (m_pipeLine) + delete m_pipeLine; + if (m_resourceBindings) + delete m_resourceBindings; + if (m_vertexBuffer) + delete m_vertexBuffer; + if (m_uniformBuffer) + delete m_uniformBuffer; +} + +void CustomRenderNode::setVertices(const QList<QVector2D> &vertices) +{ + if (m_vertices == vertices) + return; + + m_verticesDirty = true; + m_vertices = vertices; + + markDirty(QSGNode::DirtyGeometry); +} + +void CustomRenderNode::releaseResources() +{ + if (m_vertexBuffer) { + delete m_vertexBuffer; + m_vertexBuffer = nullptr; + } + + if (m_uniformBuffer) { + delete m_uniformBuffer; + m_uniformBuffer = nullptr; + } + + if (m_pipeLine) { + delete m_pipeLine; + m_pipeLine = nullptr; + } + + if (m_resourceBindings) { + delete m_resourceBindings; + m_resourceBindings = nullptr; + } + +} + +QSGRenderNode::RenderingFlags CustomRenderNode::flags() const +{ + // We are rendering 2D content directly into the scene graph + return { QSGRenderNode::NoExternalRendering | QSGRenderNode::DepthAwareRendering }; +} + +QSGRenderNode::StateFlags CustomRenderNode::changedStates() const +{ + return {QSGRenderNode::StateFlag::ViewportState | QSGRenderNode::StateFlag::CullState}; +} + +void CustomRenderNode::prepare() +{ + QSGRendererInterface *renderInterface = m_window->rendererInterface(); + QRhiSwapChain *swapChain = static_cast<QRhiSwapChain *>( + renderInterface->getResource(m_window, QSGRendererInterface::RhiSwapchainResource)); + QRhi *rhi = static_cast<QRhi *>( + renderInterface->getResource(m_window, QSGRendererInterface::RhiResource)); + Q_ASSERT(swapChain); + Q_ASSERT(rhi); + + QRhiResourceUpdateBatch *resourceUpdates = rhi->nextResourceUpdateBatch(); + + if (m_verticesDirty) { + if (m_vertexBuffer) { + delete m_vertexBuffer; + m_vertexBuffer = nullptr; + } + m_verticesDirty = false; + } + + if (!m_vertexBuffer) { + m_vertexBuffer = rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, + m_vertices.count() * sizeof(QVector2D)); + m_vertexBuffer->create(); + resourceUpdates->uploadStaticBuffer(m_vertexBuffer, m_vertices.constData()); + } + + if (!m_uniformBuffer) { + m_uniformBuffer = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68); + m_uniformBuffer->create(); + } + + if (!m_resourceBindings) { + m_resourceBindings = rhi->newShaderResourceBindings(); + m_resourceBindings->setBindings({ QRhiShaderResourceBinding::uniformBuffer( + 0, + QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, + m_uniformBuffer) }); + m_resourceBindings->create(); + } + + if (!m_pipeLine) { + + m_pipeLine = rhi->newGraphicsPipeline(); + + // + // If layer.enabled == true on our QQuickItem, the rendering face is flipped for + // backends with isYUpInFrameBuffer == true (OpenGL). This does not happen with + // RHI backends with isYUpInFrameBuffer == false. We swap the triangle winding + // order to work around this. + // + m_pipeLine->setFrontFace(QSGRenderNodePrivate::get(this)->m_rt.rt->resourceType() == QRhiResource::TextureRenderTarget + && rhi->isYUpInFramebuffer() + ? QRhiGraphicsPipeline::CW + : QRhiGraphicsPipeline::CCW); + m_pipeLine->setCullMode(QRhiGraphicsPipeline::Back); + m_pipeLine->setTopology(QRhiGraphicsPipeline::TriangleStrip); + QRhiGraphicsPipeline::TargetBlend blend; + blend.enable = true; + m_pipeLine->setTargetBlends({ blend }); + m_pipeLine->setShaderResourceBindings(m_resourceBindings); + m_pipeLine->setShaderStages(m_shaders.cbegin(), m_shaders.cend()); + m_pipeLine->setDepthTest(true); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { 2 * sizeof(float) } }); + inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } }); + m_pipeLine->setVertexInputLayout(inputLayout); + m_pipeLine->setRenderPassDescriptor(QSGRenderNodePrivate::get(this)->m_rt.rpDesc); + m_pipeLine->create(); + } + + QMatrix4x4 mvp = *projectionMatrix() * *matrix(); + float opacity = inheritedOpacity(); + + resourceUpdates->updateDynamicBuffer(m_uniformBuffer, 0, 64, mvp.constData()); + resourceUpdates->updateDynamicBuffer(m_uniformBuffer, 64, 4, &opacity); + + swapChain->currentFrameCommandBuffer()->resourceUpdate(resourceUpdates); +} + +void CustomRenderNode::render(const RenderState *state) +{ + + QSGRendererInterface *renderInterface = m_window->rendererInterface(); + QRhiSwapChain *swapChain = static_cast<QRhiSwapChain *>( + renderInterface->getResource(m_window, QSGRendererInterface::RhiSwapchainResource)); + Q_ASSERT(swapChain); + + QRhiCommandBuffer *cb = swapChain->currentFrameCommandBuffer(); + Q_ASSERT(cb); + + cb->setGraphicsPipeline(m_pipeLine); + QSize renderTargetSize = QSGRenderNodePrivate::get(this)->m_rt.rt->pixelSize(); + cb->setViewport(QRhiViewport(0, 0, renderTargetSize.width(), renderTargetSize.height())); + cb->setShaderResources(); + QRhiCommandBuffer::VertexInput vertexBindings[] = { { m_vertexBuffer, 0 } }; + cb->setVertexInput(0, 1, vertexBindings); + cb->draw(m_vertices.count()); +} + +CustomRender::CustomRender(QQuickItem *parent) : QQuickItem(parent) +{ + setFlag(ItemHasContents, true); + connect(this, &CustomRender::verticesChanged, this, &CustomRender::update); +} + +const QList<QVector2D> &CustomRender::vertices() const +{ + return m_vertices; +} + +void CustomRender::setVertices(const QList<QVector2D> &newVertices) +{ + if (m_vertices == newVertices) + return; + + m_vertices = newVertices; + emit verticesChanged(); +} + +QSGNode *CustomRender::updatePaintNode(QSGNode *old, UpdatePaintNodeData *) +{ + CustomRenderNode *node = static_cast<CustomRenderNode *>(old); + + if (!node) + node = new CustomRenderNode(window()); + + node->setVertices(m_vertices); + + return node; +} diff --git a/examples/quick/scenegraph/customrendernode/customrender.h b/examples/quick/scenegraph/customrendernode/customrender.h new file mode 100644 index 0000000000..962551c0b1 --- /dev/null +++ b/examples/quick/scenegraph/customrendernode/customrender.h @@ -0,0 +1,33 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef CUSTOMRENDER_H +#define CUSTOMRENDER_H + +#include <QQuickItem> +#include <QVector2D> + +class CustomRender : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(QList<QVector2D> vertices READ vertices WRITE setVertices NOTIFY verticesChanged) + QML_ELEMENT + +public: + explicit CustomRender(QQuickItem *parent = nullptr); + + const QList<QVector2D> &vertices() const; + void setVertices(const QList<QVector2D> &newVertices); + +signals: + + void verticesChanged(); + +protected: + QSGNode *updatePaintNode(QSGNode *old, UpdatePaintNodeData *) override; + +private: + QList<QVector2D> m_vertices; +}; + +#endif // CUSTOMRENDER_H diff --git a/examples/quick/scenegraph/customrendernode/doc/images/customrendernode-example.gif b/examples/quick/scenegraph/customrendernode/doc/images/customrendernode-example.gif Binary files differnew file mode 100644 index 0000000000..c4b36d19fb --- /dev/null +++ b/examples/quick/scenegraph/customrendernode/doc/images/customrendernode-example.gif diff --git a/examples/quick/scenegraph/customrendernode/doc/src/customrendernode.qdoc b/examples/quick/scenegraph/customrendernode/doc/src/customrendernode.qdoc new file mode 100644 index 0000000000..1972d99c4d --- /dev/null +++ b/examples/quick/scenegraph/customrendernode/doc/src/customrendernode.qdoc @@ -0,0 +1,26 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example scenegraph/customrendernode + \title Scene Graph - Custom QSGRenderNode + \ingroup qtquickexamples + \brief Shows how to use QSGRenderNode to implement custom rendering in the Qt Quick scenegraph. + + The custom render node example shows how to implement an item that is rendered using + a custom QSGRenderNode. + + \image customrendernode-example.gif + + QSGRenderNode allows direct access to the Render Hardware Interface (RHI) within the + scenegraph. This example demonstrates how to create QSGRenderNode based render node + and manage it with a custom item. The render node creates an RHI pipeline, updates + vertex and uniform buffers, and renders into the RHI command buffer. + + \warning This example demonstrates advanced, low-level functionality + performing portable, cross-platform 3D rendering, while relying on private + APIs from the Qt Gui and Qt Quick modules. Developers are encouraged to + carefully evaluate the potential lack of source and binary compatibility + guarantees before using these APIs in their applications. + + */ diff --git a/examples/quick/scenegraph/customrendernode/main.cpp b/examples/quick/scenegraph/customrendernode/main.cpp new file mode 100644 index 0000000000..5cc870b1aa --- /dev/null +++ b/examples/quick/scenegraph/customrendernode/main.cpp @@ -0,0 +1,52 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include <QGuiApplication> +#include <QtQuick/QQuickView> +#include <QSurfaceFormat> + +int main(int argc, char **argv) +{ +#ifdef Q_OS_MACOS + QSurfaceFormat format = QSurfaceFormat::defaultFormat(); + format.setMajorVersion(4); + format.setMinorVersion(1); + format.setProfile(QSurfaceFormat::CoreProfile); + QSurfaceFormat::setDefaultFormat(format); +#endif + + QGuiApplication app(argc, argv); + + QQuickView view; + + view.setResizeMode(QQuickView::SizeRootObjectToView); + view.setSource(QUrl("qrc:///scenegraph/customrendernode/main.qml")); + view.setColor(QColor(0, 0, 0)); + view.show(); + + QString api; + switch (view.graphicsApi()) { + case QSGRendererInterface::GraphicsApi::OpenGLRhi: + api = "RHI OpenGL"; + break; + case QSGRendererInterface::GraphicsApi::Direct3D11Rhi: + api = "RHI Direct3D"; + break; + case QSGRendererInterface::GraphicsApi::VulkanRhi: + api = "RHI Vulkan"; + break; + case QSGRendererInterface::GraphicsApi::MetalRhi: + api = "RHI Metal"; + break; + case QSGRendererInterface::GraphicsApi::NullRhi: + api = "RHI Null"; + break; + default: + api = "unknown"; + break; + } + + view.setTitle(QStringLiteral("Custom QSGRenderNode - ") + api); + + return QGuiApplication::exec(); +} diff --git a/examples/quick/scenegraph/customrendernode/main.qml b/examples/quick/scenegraph/customrendernode/main.qml new file mode 100644 index 0000000000..3abc10d777 --- /dev/null +++ b/examples/quick/scenegraph/customrendernode/main.qml @@ -0,0 +1,128 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +import QtQuick +import QtQuick.Controls +import SceneGraphRendering + +Item { + + id: root + width: 640 + height: 480 + + SplitView { + anchors.fill: parent + + Text { + text: "Direct" + color: 'white' + SplitView.preferredWidth: root.width/2 + + Loader { + anchors.fill: parent + sourceComponent: customComponent + } + } + + Text { + text: "Layer" + color: 'white' + SplitView.preferredWidth: root.width/2 + + Loader { + anchors.fill: parent + sourceComponent: customComponent + onLoaded: item.custom.layer.enabled=true + } + } + } + + Text { + id: description + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 20 + wrapMode: Text.WordWrap + text: "This example creates a custom scenegraph QSGRenderNode render node and " + + "demonstrates its use. The render node is placed in front of a red " + + "rectangle, and behind a white rectangle. Rendering is demonstrated " + + "directly into the scenegraph, and as a layered item. Opacity and " + + "rotation transform changes are exercised." + + Rectangle { + z:-1 + anchors.fill: parent + anchors.margins: -10 + color: 'white' + opacity: 0.5 + radius: 10 + } + } + + Component { + id: customComponent + + Item { + id: componentRoot + property alias custom: custom + + Rectangle { + width: parent.width/5 + height: parent.height/5 + anchors.centerIn: parent + color: '#ff0000' + } + + SequentialAnimation { + running: true + loops: Animation.Infinite + + OpacityAnimator { + target: custom + from: 0 + to: 1 + duration: 3500 + } + OpacityAnimator { + target: custom + from: 1 + to: 0 + duration: 3500 + } + } + + RotationAnimation { + target: custom + from: 0 + to: 360 + running: true + loops: Animation.Infinite + duration: 11000 + } + + + CustomRender { + id: custom + width: Math.min(parent.width, parent.height) + height: width + anchors.centerIn: parent + + property real a: width/2 + property real b: Math.sqrt(3.0)*a/2; + vertices: [Qt.vector2d(width/2 - a/2, height/2 + b/3), + Qt.vector2d(width/2 + a/2, height/2 + b/3), + Qt.vector2d(width/2, height/2 - b*2/3)] + + } + + Rectangle { + width: parent.width/10 + height: parent.height/10 + anchors.centerIn: parent + color: '#ffffff' + } + } + } +} diff --git a/examples/quick/scenegraph/customrendernode/shaders/customrender.frag b/examples/quick/scenegraph/customrendernode/shaders/customrender.frag new file mode 100644 index 0000000000..5d284b7696 --- /dev/null +++ b/examples/quick/scenegraph/customrendernode/shaders/customrender.frag @@ -0,0 +1,15 @@ +#version 440 + +layout(location = 0) in vec3 color; + +layout(location = 0) out vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; +}; + +void main() +{ + fragColor = vec4(color, 1.0)*qt_Opacity; +} diff --git a/examples/quick/scenegraph/customrendernode/shaders/customrender.vert b/examples/quick/scenegraph/customrendernode/shaders/customrender.vert new file mode 100644 index 0000000000..a61d6e2d71 --- /dev/null +++ b/examples/quick/scenegraph/customrendernode/shaders/customrender.vert @@ -0,0 +1,31 @@ +#version 440 + +layout(location = 0) in vec2 vertex; + +layout(location = 0) out vec3 color; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; +}; + +out gl_PerVertex { vec4 gl_Position; }; + +void main() +{ + + switch (gl_VertexIndex%3) { + case 0: + color = vec3(1.0, 0.0, 0.0); + break; + case 1: + color = vec3(0.0, 1.0, 0.0); + break; + case 2: + color = vec3(0.0, 0.0, 1.0); + break; + } + + gl_Position = qt_Matrix * vec4(vertex, 0.0, 1.0); + +} |