aboutsummaryrefslogtreecommitdiffstats
path: root/examples/quick
diff options
context:
space:
mode:
authorBen Fletcher <ben.fletcher@me.com>2022-10-16 10:59:29 -0700
committerBen Fletcher <ben.fletcher@me.com>2022-11-01 10:39:02 -0700
commit7be8566719d359ba2696f771e4ac7d802383dd9d (patch)
tree05ac78f2276d7d37d53f1370953fcb99be1009eb /examples/quick
parenta9900e9f30fa5f6c943a7c363d57c0d9d748b92f (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')
-rw-r--r--examples/quick/scenegraph/CMakeLists.txt1
-rw-r--r--examples/quick/scenegraph/customrendernode/CMakeLists.txt55
-rw-r--r--examples/quick/scenegraph/customrendernode/customrender.cpp245
-rw-r--r--examples/quick/scenegraph/customrendernode/customrender.h33
-rw-r--r--examples/quick/scenegraph/customrendernode/doc/images/customrendernode-example.gifbin0 -> 44088 bytes
-rw-r--r--examples/quick/scenegraph/customrendernode/doc/src/customrendernode.qdoc26
-rw-r--r--examples/quick/scenegraph/customrendernode/main.cpp52
-rw-r--r--examples/quick/scenegraph/customrendernode/main.qml128
-rw-r--r--examples/quick/scenegraph/customrendernode/shaders/customrender.frag15
-rw-r--r--examples/quick/scenegraph/customrendernode/shaders/customrender.vert31
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
new file mode 100644
index 0000000000..c4b36d19fb
--- /dev/null
+++ b/examples/quick/scenegraph/customrendernode/doc/images/customrendernode-example.gif
Binary files differ
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);
+
+}