From 560b5bcd277907538b71c147d12fdf3d41b1848c Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Fri, 16 Aug 2019 17:14:55 +0200 Subject: examples: Fix a few linter warnings Unqualified lookup in the root object of a component is bad. Change-Id: I7772216fb81e68824519408998c73dbb1ca60c4d Reviewed-by: Fabian Kosmale Reviewed-by: Simon Hausmann --- examples/quick/shared/CheckBox.qml | 2 +- examples/quick/shared/LauncherList.qml | 11 ++++++----- examples/quick/shared/Slider.qml | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) (limited to 'examples/quick') diff --git a/examples/quick/shared/CheckBox.qml b/examples/quick/shared/CheckBox.qml index 7b1588d2d9..51b6aabc09 100644 --- a/examples/quick/shared/CheckBox.qml +++ b/examples/quick/shared/CheckBox.qml @@ -87,7 +87,7 @@ Item { anchors.margins: frame.width / 5 fillMode: Image.PreserveAspectFit smooth: true - visible: checked + visible: root.checked } } Text { diff --git a/examples/quick/shared/LauncherList.qml b/examples/quick/shared/LauncherList.qml index fe44cf3634..9859b5b635 100644 --- a/examples/quick/shared/LauncherList.qml +++ b/examples/quick/shared/LauncherList.qml @@ -51,6 +51,7 @@ import QtQuick 2.12 Rectangle { + id: root property int activePageCount: 0 //model is a list of {"name":"somename", "url":"file:///some/url/mainfile.qml"} @@ -74,7 +75,7 @@ Rectangle { id: launcherList clip: true delegate: SimpleLauncherDelegate{ - onClicked: showExample(url) + onClicked: root.showExample(url) } model: ListModel {id:myModel} anchors.fill: parent @@ -116,7 +117,7 @@ Rectangle { ParallelAnimation { id: showAnim ScriptAction { - script: activePageCount++ + script: root.activePageCount++ } NumberAnimation { target: launcherList @@ -144,7 +145,7 @@ Rectangle { id: exitAnim ScriptAction { - script: activePageCount-- + script: root.activePageCount-- } ParallelAnimation { @@ -182,7 +183,7 @@ Rectangle { visible: height > 0 anchors.bottom: parent.bottom width: parent.width - height: activePageCount > 0 ? 40 : 0 + height: root.activePageCount > 0 ? 40 : 0 Behavior on height { NumberAnimation { @@ -222,7 +223,7 @@ Rectangle { TapHandler { id: tapHandler - enabled: activePageCount > 0 + enabled: root.activePageCount > 0 onTapped: { pageContainer.children[pageContainer.children.length - 1].exit() } diff --git a/examples/quick/shared/Slider.qml b/examples/quick/shared/Slider.qml index cdda86e39e..5b08034571 100644 --- a/examples/quick/shared/Slider.qml +++ b/examples/quick/shared/Slider.qml @@ -83,7 +83,7 @@ Item { anchors.left: parent.left anchors.leftMargin: 16 height: childrenRect.height - width: Math.max(minLabelWidth, childrenRect.width) + width: Math.max(slider.minLabelWidth, childrenRect.width) anchors.verticalCenter: parent.verticalCenter Text { text: slider.name + ":" -- cgit v1.2.3 From 7b130535cf590b310f23c8167986588d3982ad20 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Wed, 7 Aug 2019 15:27:24 +0200 Subject: Implement QSGRenderNode for the rhi path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The documentation is also refined and extended. Revise the rendernode example as well: make it possible to test and demonstrate both scissor and stencil based clipping. An implementation of the triangle for another graphics API should follow in a separate patch at a later point. For now only OpenGL is supported in combination with the RHI. Change-Id: I7ef9f6c6e0b44f1bdf44c9266ea3fa4736367a5d Reviewed-by: Christian Strømme --- .../scenegraph/rendernode/customrenderitem.cpp | 28 +++++--- .../quick/scenegraph/rendernode/d3d12renderer.cpp | 6 +- examples/quick/scenegraph/rendernode/main.qml | 81 +++++++++++++++++----- .../quick/scenegraph/rendernode/openglrenderer.cpp | 20 +++++- 4 files changed, 104 insertions(+), 31 deletions(-) (limited to 'examples/quick') diff --git a/examples/quick/scenegraph/rendernode/customrenderitem.cpp b/examples/quick/scenegraph/rendernode/customrenderitem.cpp index 8f248e2ecb..9dbe8d86a0 100644 --- a/examples/quick/scenegraph/rendernode/customrenderitem.cpp +++ b/examples/quick/scenegraph/rendernode/customrenderitem.cpp @@ -71,23 +71,29 @@ QSGNode *CustomRenderItem::updatePaintNode(QSGNode *node, UpdatePaintNodeData *) if (!ri) return nullptr; switch (ri->graphicsApi()) { - case QSGRendererInterface::OpenGL: + case QSGRendererInterface::OpenGL: + Q_FALLTHROUGH(); + case QSGRendererInterface::OpenGLRhi: #if QT_CONFIG(opengl) - n = new OpenGLRenderNode(this); - break; + n = new OpenGLRenderNode(this); #endif - case QSGRendererInterface::Direct3D12: + break; + + case QSGRendererInterface::Direct3D12: // ### Qt 6: remove #if QT_CONFIG(d3d12) - n = new D3D12RenderNode(this); - break; + n = new D3D12RenderNode(this); #endif - case QSGRendererInterface::Software: - n = new SoftwareRenderNode(this); - break; + break; + + case QSGRendererInterface::Software: + n = new SoftwareRenderNode(this); + break; - default: - return nullptr; + default: + break; } + if (!n) + qWarning("QSGRendererInterface reports unknown graphics API %d", ri->graphicsApi()); } return n; diff --git a/examples/quick/scenegraph/rendernode/d3d12renderer.cpp b/examples/quick/scenegraph/rendernode/d3d12renderer.cpp index df0e29eb67..878b022950 100644 --- a/examples/quick/scenegraph/rendernode/d3d12renderer.cpp +++ b/examples/quick/scenegraph/rendernode/d3d12renderer.cpp @@ -54,6 +54,8 @@ #include #include +// ### Qt 6: remove + #if QT_CONFIG(d3d12) D3D12RenderNode::D3D12RenderNode(QQuickItem *item) @@ -166,8 +168,8 @@ void D3D12RenderNode::init() psoDesc.RasterizerState = rastDesc; psoDesc.BlendState = blendDesc; // No depth. The correct stacking of the item is ensured by the projection matrix. - // Do not bother with stencil since we do not apply clipping in the - // example. If clipping is desired, render() needs to set a different PSO + // Note that this does not support clipping. + // If clipping is desired, render() needs to set a different PSO // with stencil enabled whenever the RenderState indicates so. psoDesc.SampleMask = UINT_MAX; psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; diff --git a/examples/quick/scenegraph/rendernode/main.qml b/examples/quick/scenegraph/rendernode/main.qml index d0ba4a4669..85060261c5 100644 --- a/examples/quick/scenegraph/rendernode/main.qml +++ b/examples/quick/scenegraph/rendernode/main.qml @@ -53,23 +53,55 @@ import SceneGraphRendering 2.0 Item { Rectangle { + id: bg anchors.fill: parent gradient: Gradient { GradientStop { position: 0; color: "steelblue" } GradientStop { position: 1; color: "black" } } - CustomRenderItem { - id: renderer + MouseArea { anchors.fill: parent - anchors.margins: 10 + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: { + if (mouse.button === Qt.LeftButton) { + clipper.clip = !clipper.clip + } else if (mouse.button === Qt.RightButton) { + nonRectClipAnim.running = !nonRectClipAnim.running + if (!nonRectClipAnim.running) + clipper.rotation = 0; + } + } + } + + Rectangle { + id: clipper + width: parent.width / 2 + height: parent.height / 2 + anchors.centerIn: parent + border.color: "yellow" + border.width: 2 + color: "transparent" + NumberAnimation on rotation { + id: nonRectClipAnim + from: 0; to: 360; duration: 5000; loops: Animation.Infinite + running: false + } + + CustomRenderItem { + id: renderer + width: bg.width - 20 + height: bg.height - 20 + x: -clipper.x + 10 + y: -clipper.y + 10 - transform: [ - Rotation { id: rotation; axis.x: 0; axis.z: 0; axis.y: 1; angle: 0; origin.x: renderer.width / 2; origin.y: renderer.height / 2; }, - Translate { id: txOut; x: -renderer.width / 2; y: -renderer.height / 2 }, - Scale { id: scale; }, - Translate { id: txIn; x: renderer.width / 2; y: renderer.height / 2 } - ] + transform: [ + Rotation { id: rotation; axis.x: 0; axis.z: 0; axis.y: 1; angle: 0; origin.x: renderer.width / 2; origin.y: renderer.height / 2; }, + Translate { id: txOut; x: -renderer.width / 2; y: -renderer.height / 2 }, + Scale { id: scale; }, + Translate { id: txIn; x: renderer.width / 2; y: renderer.height / 2 } + ] + } } SequentialAnimation { @@ -94,17 +126,34 @@ Item { Text { id: label - anchors.bottom: renderer.bottom - anchors.left: renderer.left - anchors.right: renderer.right + anchors.bottom: parent.bottom + anchors.left: parent.left anchors.margins: 20 + color: "yellow" wrapMode: Text.WordWrap property int api: GraphicsInfo.api - text: "Custom rendering via the graphics API " - + (api === GraphicsInfo.OpenGL ? "OpenGL" - : api === GraphicsInfo.Direct3D12 ? "Direct3D 12" - : api === GraphicsInfo.Software ? "Software" : "") + text: { + var apiStr; + switch (api) { + case GraphicsInfo.OpenGL: apiStr = "OpenGL (direct)"; break; + case GraphicsInfo.Direct3D12: apiStr = "Direct3D 12 (direct)"; break; + case GraphicsInfo.Software: apiStr = "Software (QPainter)"; break; + case GraphicsInfo.OpenGLRhi: apiStr = "OpenGL (RHI)"; break; + // the example has no other QSGRenderNode subclasses + default: apiStr = ""; break; + } + "Custom rendering via the graphics API " + apiStr + + "\nLeft click to toggle clipping to yellow rect" + + "\nRight click to rotate (can be used to exercise stencil clip instead of scissor)" + } + } + + Text { + id: label2 + anchors.top: parent.top + anchors.right: parent.right color: "yellow" + text: "Clip: " + (clipper.clip ? "ON" : "OFF") + " Rotation: " + (nonRectClipAnim.running ? "ON" : "OFF") } } } diff --git a/examples/quick/scenegraph/rendernode/openglrenderer.cpp b/examples/quick/scenegraph/rendernode/openglrenderer.cpp index 3c68830db6..5e6b9e3656 100644 --- a/examples/quick/scenegraph/rendernode/openglrenderer.cpp +++ b/examples/quick/scenegraph/rendernode/openglrenderer.cpp @@ -148,17 +148,33 @@ void OpenGLRenderNode::render(const RenderState *state) m_program->enableAttributeArray(0); m_program->enableAttributeArray(1); - // Note that clipping (scissor or stencil) is ignored in this example. + // We are prepared both for the legacy (direct OpenGL) and the modern + // (abstracted by RHI) OpenGL scenegraph. So set all the states that are + // important to us. + + f->glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); f->glEnable(GL_BLEND); f->glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + // Clip support. + if (state->scissorEnabled()) { + f->glEnable(GL_SCISSOR_TEST); + const QRect r = state->scissorRect(); // already bottom-up + f->glScissor(r.x(), r.y(), r.width(), r.height()); + } + if (state->stencilEnabled()) { + f->glEnable(GL_STENCIL_TEST); + f->glStencilFunc(GL_EQUAL, state->stencilValue(), 0xFF); + f->glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + } + f->glDrawArrays(GL_TRIANGLES, 0, 3); } QSGRenderNode::StateFlags OpenGLRenderNode::changedStates() const { - return BlendState; + return BlendState | ScissorState | StencilState; } QSGRenderNode::RenderingFlags OpenGLRenderNode::flags() const -- cgit v1.2.3 From ef2715251e1785e273873e4000ed08fd99962ab7 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Fri, 16 Aug 2019 09:54:03 +0200 Subject: Add missing scenegraph example docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: I72e18136a26cdfb52f204ce7d0491d14411956a4 Reviewed-by: Christian Strømme --- .../doc/images/d3d11underqml-example.jpg | Bin 0 -> 79343 bytes .../d3d11underqml/doc/src/d3d11underqml.qdoc | 58 ++++++++ .../doc/images/metalunderqml-example.jpg | Bin 0 -> 55194 bytes .../metalunderqml/doc/src/metalunderqml.qdoc | 68 +++++++++ .../openglunderqml/doc/src/openglunderqml.qdoc | 5 + .../scenegraph/rendernode/customrenderitem.cpp | 4 + .../rendernode/doc/images/rendernode-example.jpg | Bin 0 -> 77210 bytes .../scenegraph/rendernode/doc/src/rendernode.qdoc | 163 +++++++++++++++++++++ examples/quick/scenegraph/rendernode/main.cpp | 2 + examples/quick/scenegraph/rendernode/main.qml | 8 + .../quick/scenegraph/rendernode/openglrenderer.cpp | 11 +- .../quick/scenegraph/rendernode/openglrenderer.h | 2 + 12 files changed, 320 insertions(+), 1 deletion(-) create mode 100644 examples/quick/scenegraph/d3d11underqml/doc/images/d3d11underqml-example.jpg create mode 100644 examples/quick/scenegraph/d3d11underqml/doc/src/d3d11underqml.qdoc create mode 100644 examples/quick/scenegraph/metalunderqml/doc/images/metalunderqml-example.jpg create mode 100644 examples/quick/scenegraph/metalunderqml/doc/src/metalunderqml.qdoc create mode 100644 examples/quick/scenegraph/rendernode/doc/images/rendernode-example.jpg create mode 100644 examples/quick/scenegraph/rendernode/doc/src/rendernode.qdoc (limited to 'examples/quick') diff --git a/examples/quick/scenegraph/d3d11underqml/doc/images/d3d11underqml-example.jpg b/examples/quick/scenegraph/d3d11underqml/doc/images/d3d11underqml-example.jpg new file mode 100644 index 0000000000..9f1e53ad61 Binary files /dev/null and b/examples/quick/scenegraph/d3d11underqml/doc/images/d3d11underqml-example.jpg differ diff --git a/examples/quick/scenegraph/d3d11underqml/doc/src/d3d11underqml.qdoc b/examples/quick/scenegraph/d3d11underqml/doc/src/d3d11underqml.qdoc new file mode 100644 index 0000000000..d7b60d3b81 --- /dev/null +++ b/examples/quick/scenegraph/d3d11underqml/doc/src/d3d11underqml.qdoc @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \example scenegraph/d3d11underqml + \title Scene Graph - Direct3D 11 Under QML + \ingroup qtquickexamples + \brief Shows how to render directly with Direct3D 11 under a Qt Quick scene. + + \image d3d11underqml-example.jpg + + The Direct3D 11 Under QML example shows how an application can make use + of the \l QQuickWindow::beforeRendering() signal to draw custom + D3D11 content under a Qt Quick scene. This signal is emitted at + the start of every frame, before the scene graph starts its + rendering, thus any D3D11 draw calls that are made as a response + to this signal, will stack under the Qt Quick items. + + As an alternative, applications that wish to render D3D11 content + on top of the Qt Quick scene, can do so by connecting to the \l + QQuickWindow::afterRendering() signal. + + In this example, we will also see how it is possible to have + values that are exposed to QML which affect the D3D11 + rendering. We animate the threshold value using a NumberAnimation + in the QML file and this value is used by the HLSL shader + program that draws the squircles. + + The example is equivalent in most ways to the \l{Scene Graph - OpenGL Under + QML}{OpenGL Under QML} and \l{Scene Graph - Metal Under QML}{Metal Under + QML} examples, they all render the same custom content, just via different + native APIs. + + */ diff --git a/examples/quick/scenegraph/metalunderqml/doc/images/metalunderqml-example.jpg b/examples/quick/scenegraph/metalunderqml/doc/images/metalunderqml-example.jpg new file mode 100644 index 0000000000..98085773de Binary files /dev/null and b/examples/quick/scenegraph/metalunderqml/doc/images/metalunderqml-example.jpg differ diff --git a/examples/quick/scenegraph/metalunderqml/doc/src/metalunderqml.qdoc b/examples/quick/scenegraph/metalunderqml/doc/src/metalunderqml.qdoc new file mode 100644 index 0000000000..d499f47de3 --- /dev/null +++ b/examples/quick/scenegraph/metalunderqml/doc/src/metalunderqml.qdoc @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \example scenegraph/metalunderqml + \title Scene Graph - Metal Under QML + \ingroup qtquickexamples + \brief Shows how to render directly with Metal under a Qt Quick scene. + + \image metalunderqml-example.jpg + + The Metal Under QML example shows how an application can make use + of the \l QQuickWindow::beforeRendering() and \l + QQuickWindow::beforeRenderPassRecording() signals to draw custom + Metal content under a Qt Quick scene. This signal is emitted at + the start of every frame, before the scene graph starts its + rendering, thus any Metal draw calls that are made as a response + to this signal, will stack under the Qt Quick items. There are two + signals, because the custom Metal commands are recorded onto the + same command buffer with the same render command encoder that the + scene graph uses. beforeRendering() on its own is not sufficient + for this because it gets emitted at the start of the frame, before + having an + \l{https://developer.apple.com/documentation/metal/mtlrendercommandencoder}{MTLRenderCommandEncoder} + available. By also connecting to beforeRenderPassRecording(), the + application can gain access to the necessary native objects. + + As an alternative, applications that wish to render Metal 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 Metal + rendering. We animate the threshold value using a NumberAnimation + in the QML file and this value is used by the Metal shader + program that draws the squircles. + + The example is equivalent in most ways to the \l{Scene Graph - OpenGL Under + QML}{OpenGL Under QML} and \l{Scene Graph - Direct3D 11 Under QML}{Direct3D + 11 Under QML} examples, they all render the same custom content, just via + different native APIs. + + */ diff --git a/examples/quick/scenegraph/openglunderqml/doc/src/openglunderqml.qdoc b/examples/quick/scenegraph/openglunderqml/doc/src/openglunderqml.qdoc index 69a9d2ce4b..ed46b40420 100644 --- a/examples/quick/scenegraph/openglunderqml/doc/src/openglunderqml.qdoc +++ b/examples/quick/scenegraph/openglunderqml/doc/src/openglunderqml.qdoc @@ -50,6 +50,11 @@ in the QML file and this value is used by the OpenGL shader program that draws the squircles. + The example is equivalent in most ways to the \l{Scene Graph - Direct3D 11 Under + QML}{Direct3D 11 Under QML} and \l{Scene Graph - Metal Under QML}{Metal Under + QML} examples, they all render the same custom content, just via different + native APIs. + \snippet scenegraph/openglunderqml/squircle.h 2 First of all, we need an object we can expose to QML. This is a diff --git a/examples/quick/scenegraph/rendernode/customrenderitem.cpp b/examples/quick/scenegraph/rendernode/customrenderitem.cpp index 9dbe8d86a0..7ba9286afa 100644 --- a/examples/quick/scenegraph/rendernode/customrenderitem.cpp +++ b/examples/quick/scenegraph/rendernode/customrenderitem.cpp @@ -56,13 +56,16 @@ #include "d3d12renderer.h" #include "softwarerenderer.h" +//! [1] CustomRenderItem::CustomRenderItem(QQuickItem *parent) : QQuickItem(parent) { // Our item shows something so set the flag. setFlag(ItemHasContents); } +//! [1] +//! [2] QSGNode *CustomRenderItem::updatePaintNode(QSGNode *node, UpdatePaintNodeData *) { QSGRenderNode *n = static_cast(node); @@ -98,3 +101,4 @@ QSGNode *CustomRenderItem::updatePaintNode(QSGNode *node, UpdatePaintNodeData *) return n; } +//! [2] diff --git a/examples/quick/scenegraph/rendernode/doc/images/rendernode-example.jpg b/examples/quick/scenegraph/rendernode/doc/images/rendernode-example.jpg new file mode 100644 index 0000000000..cbb59b950d Binary files /dev/null and b/examples/quick/scenegraph/rendernode/doc/images/rendernode-example.jpg differ diff --git a/examples/quick/scenegraph/rendernode/doc/src/rendernode.qdoc b/examples/quick/scenegraph/rendernode/doc/src/rendernode.qdoc new file mode 100644 index 0000000000..e7864387cc --- /dev/null +++ b/examples/quick/scenegraph/rendernode/doc/src/rendernode.qdoc @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \example scenegraph/rendernode + \title Scene Graph - Custom Rendering with QSGRenderNode + \ingroup qtquickexamples + \brief Shows how to integrate drawing via the native graphics API with the Qt Quick scene graph. + + \image rendernode-example.jpg + + \l QSGRenderNode allows integrating draw and other calls made directly via + the Qt Quick scene graph's underlying native graphics API (such as, Vulkan, + Metal, Direct 3D, or OpenGL). This example demonstrates implementing a + custom QQuickItem backed by a QSGRenderNode implementation, where the node + renders a triangle directly via the graphics API. The rest of the scene + (background, text, rectangles) are standard Qt Quick items. + + The custom item behaves like any other Qt Quick item, meaning it + participates and stacking and clipping as usual, which is a big difference + to the alternative approaches like having the custom rendering as an + overlay (connecting to \l QQuickWindow::afterRendering()) and underlay + (connecting to \l QQuickWindow::beforeRendering()) because those do not + offer the possibility of proper mixing of the custom content with the Qt + Quick scene. + + Another important feature is that QSGRenderNode can be helpful to preserve + performance, when compared to some of the alternatives. Going through \l + QQuickFramebufferObject allows creating a custom item similarly to what + this example does, but it does it by rendering the custom content in a + texture, and then drawing a textured quad with that texture. This can be + expensive on some systems due to the cost of texturing and blending. + QSGRenderNode avoids this since the native graphics calls are issued in + line with the draw calls for the scene graph's batches. + + All this comes at the cost of being more complex, and not necessarily being + suitable for all types of 3D content, in particular where vertices and + different depth would clash with the 2D content in the Qt Quick scene + graph's batches (those are better served by "flattening" into a 2D texture + via approaches like QQuickFramebufferObject). Therefore QSGRenderNode is + not always the right choice. It can however a good and powerful choice in + many cases. This is what the example demonstrates. + + Let's go through the most important parts of the code: + + \snippet scenegraph/rendernode/main.cpp 1 + + Our custom QML type is implemented in the class CustomRenderItem. + + \snippet scenegraph/rendernode/main.qml 2 + + The corresponding import in the QML document. + + \snippet scenegraph/rendernode/main.qml 3 + + The CustomRenderItem object. It is positioned to fill a big part of the + scene, covering its parent (the yellow rectangle; this will be used to + demonstrate clipping). The item will have its scale and rotation animated. + + \snippet scenegraph/rendernode/main.qml 4 + + Text items are used to show some helpful information, such as, the + active graphics API Qt Quick uses. + + \snippet scenegraph/rendernode/main.qml 5 + + Clicking the left mouse button is used to toggle clipping on the custom + item's parent item. By default this is done using scissoring (GL_SCISSOR_TEST + with OpenGL). A well-written QSGRenderNode implementation is expected to be + able to take this into account and enable scissor testing when the scene graph + indicates that it is necessary. + + The right mouse button is used to toggle an animation on the rotation of + the parent item. With clipping enabled, this demonstrates clipping via the + stencil buffer since a rectangular scissor is not appropriate when we need + to clip to a rotated rectangle shape. The scene graph fills up the stencil + buffer as necessary, the QSGRenderNode implementation just has to enable + stencil testing using the provided reference value. + + \snippet scenegraph/rendernode/customrenderitem.cpp 1 + + Moving on to the CustomRenderItem implementation. This is a visual item. + + \snippet scenegraph/rendernode/customrenderitem.cpp 2 + + The implementation of \l QQuickItem::updatePaintNode() creates (if not yet + done) and returns an instance of a suitable QSGRenderNode subclass. The + example supports multiple graphics APIs, and also the \c software backend. + + Let's look at the the render node for OpenGL (supporting both the + traditional, direct OpenGL-based scene graph, and also the modern, + abstracted variant using the RHI). For other graphics APIs, the concepts + and the outline of a QSGRenderNode implementation are the same. It is worth + noting that in some cases it will also be necessary to connect to a signal + like \l QQuickWindow::beforeRendering() to perform copy type of operations + (such as, vertex buffer uploads). This is not necessary for OpenGL, but it + is essential for Vulkan or Metal since there such operations cannot be + issued in render() as there is a renderpass being recorded when render() is + called. + + \snippet scenegraph/rendernode/openglrenderer.h 1 + + The main job is to provide implementations of the virtual QSGRenderNode functions. + + \snippet scenegraph/rendernode/openglrenderer.cpp 1 + + The pattern for safe graphics resource management is to do any cleanup in + \l{QSGRenderNode::releaseResources()}{releaseResources()}, while also + calling this from the destructor. + + \snippet scenegraph/rendernode/openglrenderer.cpp 2 + + The render() function initializes graphics resources (in this case, an + OpenGL shader program and a vertex buffer), if not yet done. It then + makes sure the necessary resources are bound and updates uniforms. + The transformation matrix and the opacity are provided by the scene graph + either via the \c state argument or base class functions. + + \snippet scenegraph/rendernode/openglrenderer.cpp 5 + + This render node is well-behaving since it basically renders in 2D, + respecting the item's geometry. This is not mandatory, but then flags() has + to return (or not return) the appropriate flags. + + \snippet scenegraph/rendernode/openglrenderer.cpp 3 + + After setting up vertex inputs, but before recording a draw call for our + triangle, it is important to set some state in order to integrate with the + rest of the scene correctly. Setting scissor and stencil as instructed by + \c state allows our item to render correctly even when there are one or + more clips in the parent chain. + + \snippet scenegraph/rendernode/openglrenderer.cpp 4 + + As shown above, we only really render in 2D (no depth), within the item's + geometry. changedStates() returns the flags corresponding to the OpenGL + states render() touches. + +*/ diff --git a/examples/quick/scenegraph/rendernode/main.cpp b/examples/quick/scenegraph/rendernode/main.cpp index 21419abfc9..146d787e50 100644 --- a/examples/quick/scenegraph/rendernode/main.cpp +++ b/examples/quick/scenegraph/rendernode/main.cpp @@ -58,7 +58,9 @@ int main(int argc, char **argv) { QGuiApplication app(argc, argv); +//! [1] qmlRegisterType("SceneGraphRendering", 2, 0, "CustomRenderItem"); +//! [1] QQuickView view; diff --git a/examples/quick/scenegraph/rendernode/main.qml b/examples/quick/scenegraph/rendernode/main.qml index 85060261c5..c89d6136c7 100644 --- a/examples/quick/scenegraph/rendernode/main.qml +++ b/examples/quick/scenegraph/rendernode/main.qml @@ -49,7 +49,9 @@ ****************************************************************************/ import QtQuick 2.8 +//! [2] import SceneGraphRendering 2.0 +//! [2] Item { Rectangle { @@ -60,6 +62,7 @@ Item { GradientStop { position: 1; color: "black" } } + //! [5] MouseArea { anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton @@ -73,6 +76,7 @@ Item { } } } + // ![5] Rectangle { id: clipper @@ -88,6 +92,7 @@ Item { running: false } + //! [3] CustomRenderItem { id: renderer width: bg.width - 20 @@ -102,6 +107,7 @@ Item { Translate { id: txIn; x: renderer.width / 2; y: renderer.height / 2 } ] } + //! [3] } SequentialAnimation { @@ -124,6 +130,7 @@ Item { loops: Animation.Infinite } + //! [4] Text { id: label anchors.bottom: parent.bottom @@ -146,6 +153,7 @@ Item { + "\nLeft click to toggle clipping to yellow rect" + "\nRight click to rotate (can be used to exercise stencil clip instead of scissor)" } + // ![4] } Text { diff --git a/examples/quick/scenegraph/rendernode/openglrenderer.cpp b/examples/quick/scenegraph/rendernode/openglrenderer.cpp index 5e6b9e3656..0633731617 100644 --- a/examples/quick/scenegraph/rendernode/openglrenderer.cpp +++ b/examples/quick/scenegraph/rendernode/openglrenderer.cpp @@ -57,6 +57,7 @@ #include #include +//! [1] OpenGLRenderNode::OpenGLRenderNode(QQuickItem *item) : m_item(item) { @@ -74,6 +75,7 @@ void OpenGLRenderNode::releaseResources() delete m_vbo; m_vbo = nullptr; } +//! [1] void OpenGLRenderNode::init() { @@ -121,19 +123,21 @@ void OpenGLRenderNode::init() m_vbo->release(); } +//! [2] void OpenGLRenderNode::render(const RenderState *state) { if (!m_program) init(); QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); - m_program->bind(); m_program->setUniformValue(m_matrixUniform, *state->projectionMatrix() * *matrix()); m_program->setUniformValue(m_opacityUniform, float(inheritedOpacity())); +//! [2] m_vbo->bind(); +//! [5] QPointF p0(m_item->width() - 1, m_item->height() - 1); QPointF p1(0, 0); QPointF p2(0, m_item->height() - 1); @@ -142,6 +146,7 @@ void OpenGLRenderNode::render(const RenderState *state) GLfloat(p1.x()), GLfloat(p1.y()), GLfloat(p2.x()), GLfloat(p2.y()) }; m_vbo->write(0, vertices, sizeof(vertices)); +//! [5] m_program->setAttributeBuffer(0, GL_FLOAT, 0, 2); m_program->setAttributeBuffer(1, GL_FLOAT, sizeof(vertices), 3); @@ -152,6 +157,7 @@ void OpenGLRenderNode::render(const RenderState *state) // (abstracted by RHI) OpenGL scenegraph. So set all the states that are // important to us. + //! [3] f->glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); f->glEnable(GL_BLEND); @@ -170,8 +176,10 @@ void OpenGLRenderNode::render(const RenderState *state) } f->glDrawArrays(GL_TRIANGLES, 0, 3); + //! [3] } +//! [4] QSGRenderNode::StateFlags OpenGLRenderNode::changedStates() const { return BlendState | ScissorState | StencilState; @@ -186,5 +194,6 @@ QRectF OpenGLRenderNode::rect() const { return QRect(0, 0, m_item->width(), m_item->height()); } +//! [4] #endif // opengl diff --git a/examples/quick/scenegraph/rendernode/openglrenderer.h b/examples/quick/scenegraph/rendernode/openglrenderer.h index ac640405c5..8d2d3caad1 100644 --- a/examples/quick/scenegraph/rendernode/openglrenderer.h +++ b/examples/quick/scenegraph/rendernode/openglrenderer.h @@ -63,6 +63,7 @@ class QOpenGLBuffer; QT_END_NAMESPACE +//! [1] class OpenGLRenderNode : public QSGRenderNode { public: @@ -74,6 +75,7 @@ public: StateFlags changedStates() const override; RenderingFlags flags() const override; QRectF rect() const override; +//! [1] private: void init(); -- cgit v1.2.3 From 7dcdf3b7ae27cd26b84731941f5b1f380c5b3316 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Fri, 16 Aug 2019 14:21:15 +0200 Subject: rendernode example: Add support for Metal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plus clarify QQuickWindow::begin/endExternalCommands() in combination with QSGRenderNode in the docs. As the example demonstrates, calling these functions is not necessary within render() of a render node. Also fix an issue with resetting the scissor in the renderer after calling render() of a QSGRenderNode. Change-Id: If8c2dab38d62aa444266d37901f062a51e767f68 Reviewed-by: Christian Strømme --- .../scenegraph/rendernode/customrenderitem.cpp | 16 + .../scenegraph/rendernode/doc/src/rendernode.qdoc | 4 +- examples/quick/scenegraph/rendernode/main.qml | 1 + .../quick/scenegraph/rendernode/metalrenderer.h | 100 +++++++ .../quick/scenegraph/rendernode/metalrenderer.mm | 326 +++++++++++++++++++++ .../quick/scenegraph/rendernode/metalshader.frag | 28 ++ .../quick/scenegraph/rendernode/metalshader.vert | 31 ++ .../quick/scenegraph/rendernode/rendernode.pro | 6 + .../quick/scenegraph/rendernode/rendernode.qrc | 2 + 9 files changed, 513 insertions(+), 1 deletion(-) create mode 100644 examples/quick/scenegraph/rendernode/metalrenderer.h create mode 100644 examples/quick/scenegraph/rendernode/metalrenderer.mm create mode 100644 examples/quick/scenegraph/rendernode/metalshader.frag create mode 100644 examples/quick/scenegraph/rendernode/metalshader.vert (limited to 'examples/quick') diff --git a/examples/quick/scenegraph/rendernode/customrenderitem.cpp b/examples/quick/scenegraph/rendernode/customrenderitem.cpp index 7ba9286afa..67a9cccfc6 100644 --- a/examples/quick/scenegraph/rendernode/customrenderitem.cpp +++ b/examples/quick/scenegraph/rendernode/customrenderitem.cpp @@ -53,6 +53,7 @@ #include #include "openglrenderer.h" +#include "metalrenderer.h" #include "d3d12renderer.h" #include "softwarerenderer.h" @@ -82,6 +83,18 @@ QSGNode *CustomRenderItem::updatePaintNode(QSGNode *node, UpdatePaintNodeData *) #endif break; + case QSGRendererInterface::MetalRhi: +#ifdef Q_OS_DARWIN + { + MetalRenderNode *metalNode = new MetalRenderNode(this); + n = metalNode; + metalNode->resourceBuilder()->setWindow(window()); + QObject::connect(window(), &QQuickWindow::beforeRendering, + metalNode->resourceBuilder(), &MetalRenderNodeResourceBuilder::build); + } +#endif + break; + case QSGRendererInterface::Direct3D12: // ### Qt 6: remove #if QT_CONFIG(d3d12) n = new D3D12RenderNode(this); @@ -102,3 +115,6 @@ QSGNode *CustomRenderItem::updatePaintNode(QSGNode *node, UpdatePaintNodeData *) return n; } //! [2] + +// This item does not support being moved between windows. If that is desired, +// itemChange() should be reimplemented as well. diff --git a/examples/quick/scenegraph/rendernode/doc/src/rendernode.qdoc b/examples/quick/scenegraph/rendernode/doc/src/rendernode.qdoc index e7864387cc..ba6551fddf 100644 --- a/examples/quick/scenegraph/rendernode/doc/src/rendernode.qdoc +++ b/examples/quick/scenegraph/rendernode/doc/src/rendernode.qdoc @@ -38,7 +38,9 @@ Metal, Direct 3D, or OpenGL). This example demonstrates implementing a custom QQuickItem backed by a QSGRenderNode implementation, where the node renders a triangle directly via the graphics API. The rest of the scene - (background, text, rectangles) are standard Qt Quick items. + (background, text, rectangles) are standard Qt Quick items. The example has + full support for OpenGL and Metal, as well as the software backend of Qt + Quick. The custom item behaves like any other Qt Quick item, meaning it participates and stacking and clipping as usual, which is a big difference diff --git a/examples/quick/scenegraph/rendernode/main.qml b/examples/quick/scenegraph/rendernode/main.qml index c89d6136c7..153a71e097 100644 --- a/examples/quick/scenegraph/rendernode/main.qml +++ b/examples/quick/scenegraph/rendernode/main.qml @@ -146,6 +146,7 @@ Item { case GraphicsInfo.Direct3D12: apiStr = "Direct3D 12 (direct)"; break; case GraphicsInfo.Software: apiStr = "Software (QPainter)"; break; case GraphicsInfo.OpenGLRhi: apiStr = "OpenGL (RHI)"; break; + case GraphicsInfo.MetalRhi: apiStr = "Metal (RHI)"; break; // the example has no other QSGRenderNode subclasses default: apiStr = ""; break; } diff --git a/examples/quick/scenegraph/rendernode/metalrenderer.h b/examples/quick/scenegraph/rendernode/metalrenderer.h new file mode 100644 index 0000000000..77c9892313 --- /dev/null +++ b/examples/quick/scenegraph/rendernode/metalrenderer.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef METALRENDERER_H +#define METALRENDERER_H + +#include + +#ifdef Q_OS_DARWIN + +QT_BEGIN_NAMESPACE + +class QQuickItem; +class QQuickWindow; + +QT_END_NAMESPACE + +class MetalRenderNodeResourceBuilder : public QObject +{ + Q_OBJECT + +public: + void setWindow(QQuickWindow *w) { m_window = w; } + +public slots: + void build(); + +private: + QQuickWindow *m_window = nullptr; +}; + +class MetalRenderNode : public QSGRenderNode +{ +public: + MetalRenderNode(QQuickItem *item); + ~MetalRenderNode(); + + void render(const RenderState *state) override; + void releaseResources() override; + StateFlags changedStates() const override; + RenderingFlags flags() const override; + QRectF rect() const override; + + MetalRenderNodeResourceBuilder *resourceBuilder() { return &m_resourceBuilder; } + +private: + QQuickItem *m_item; + MetalRenderNodeResourceBuilder m_resourceBuilder; +}; + +#endif // Q_OS_DARWIN + +#endif diff --git a/examples/quick/scenegraph/rendernode/metalrenderer.mm b/examples/quick/scenegraph/rendernode/metalrenderer.mm new file mode 100644 index 0000000000..4cb973abee --- /dev/null +++ b/examples/quick/scenegraph/rendernode/metalrenderer.mm @@ -0,0 +1,326 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "metalrenderer.h" +#include +#include + +#include + +using FuncAndLib = QPair, id >; + +const int MAX_FRAMES_IN_FLIGHT = 3; + +struct { + id dev = nil; + QByteArray vsSource; + FuncAndLib vs; + QByteArray fsSource; + FuncAndLib fs; + id vbuf[MAX_FRAMES_IN_FLIGHT]; + id ubuf[MAX_FRAMES_IN_FLIGHT]; + id stencilEnabledDsState = nil; + id pipeline = nil; +} g; + +static FuncAndLib compileShaderFromSource(const QByteArray &src, const QByteArray &entryPoint) +{ + FuncAndLib fl; + + NSString *srcstr = [NSString stringWithUTF8String: src.constData()]; + MTLCompileOptions *opts = [[MTLCompileOptions alloc] init]; + opts.languageVersion = MTLLanguageVersion1_2; + NSError *err = nil; + fl.second = [g.dev newLibraryWithSource: srcstr options: opts error: &err]; + [opts release]; + // srcstr is autoreleased + + if (err) { + const QString msg = QString::fromNSString(err.localizedDescription); + qFatal("%s", qPrintable(msg)); + return fl; + } + + NSString *name = [NSString stringWithUTF8String: entryPoint.constData()]; + fl.first = [fl.second newFunctionWithName: name]; + [name release]; + + return fl; +} + +const int VERTEX_SIZE = 6 * sizeof(float); + +static float colors[] = { + 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f +}; + +void MetalRenderNodeResourceBuilder::build() +{ + if (!g.dev) { + QSGRendererInterface *rif = m_window->rendererInterface(); + Q_ASSERT(rif->graphicsApi() == QSGRendererInterface::MetalRhi); + + g.dev = (id) rif->getResource(m_window, QSGRendererInterface::DeviceResource); + Q_ASSERT(g.dev); + } + + if (g.vsSource.isEmpty()) { + const QString filename = QLatin1String(":/scenegraph/rendernode/metalshader.vert"); + QFile f(filename); + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) + qFatal("Failed to read shader %s", qPrintable(filename)); + g.vsSource = f.readAll(); + g.vs = compileShaderFromSource(g.vsSource, QByteArrayLiteral("main0")); + } + + if (g.fsSource.isEmpty()) { + const QString filename = QLatin1String(":/scenegraph/rendernode/metalshader.frag"); + QFile f(filename); + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) + qFatal("Failed to read shader %s", qPrintable(filename)); + g.fsSource = f.readAll(); + g.fs = compileShaderFromSource(g.fsSource, QByteArrayLiteral("main0")); + } + + const int framesInFlight = m_window->graphicsStateInfo()->framesInFlight; + + // For simplicity's sake we use shared mode (something like host visible + + // host coherent) for everything. + + for (int i = 0; i < framesInFlight; ++i) { + // Have multiple versions for vertex too since we'll just memcpy new + // vertices based on item width and height on every render(). This could + // be optimized further however. + if (!g.vbuf[i]) { + g.vbuf[i] = [g.dev newBufferWithLength: VERTEX_SIZE + sizeof(colors) options: MTLResourceStorageModeShared]; + char *p = (char *) [g.vbuf[i] contents]; + memcpy(p + VERTEX_SIZE, colors, sizeof(colors)); + } + + if (!g.ubuf[i]) + g.ubuf[i] = [g.dev newBufferWithLength: 256 options: MTLResourceStorageModeShared]; + } + + if (!g.stencilEnabledDsState) { + MTLDepthStencilDescriptor *dsDesc = [[MTLDepthStencilDescriptor alloc] init]; + dsDesc.frontFaceStencil = [[MTLStencilDescriptor alloc] init]; + dsDesc.frontFaceStencil.stencilFailureOperation = MTLStencilOperationKeep; + dsDesc.frontFaceStencil.depthFailureOperation = MTLStencilOperationKeep; + dsDesc.frontFaceStencil.depthStencilPassOperation = MTLStencilOperationKeep; + dsDesc.frontFaceStencil.stencilCompareFunction = MTLCompareFunctionEqual; + dsDesc.frontFaceStencil.readMask = 0xFF; + dsDesc.frontFaceStencil.writeMask = 0xFF; + + dsDesc.backFaceStencil = [[MTLStencilDescriptor alloc] init]; + dsDesc.backFaceStencil.stencilFailureOperation = MTLStencilOperationKeep; + dsDesc.backFaceStencil.depthFailureOperation = MTLStencilOperationKeep; + dsDesc.backFaceStencil.depthStencilPassOperation = MTLStencilOperationKeep; + dsDesc.backFaceStencil.stencilCompareFunction = MTLCompareFunctionEqual; + dsDesc.backFaceStencil.readMask = 0xFF; + dsDesc.backFaceStencil.writeMask = 0xFF; + + g.stencilEnabledDsState = [g.dev newDepthStencilStateWithDescriptor: dsDesc]; + [dsDesc release]; + } + + if (!g.pipeline) { + MTLVertexDescriptor *inputLayout = [MTLVertexDescriptor vertexDescriptor]; + inputLayout.attributes[0].format = MTLVertexFormatFloat2; + inputLayout.attributes[0].offset = 0; + inputLayout.attributes[0].bufferIndex = 1; // ubuf is 0, vbuf is 1 and 2 + inputLayout.attributes[1].format = MTLVertexFormatFloat3; + inputLayout.attributes[1].offset = 0; + inputLayout.attributes[1].bufferIndex = 2; + inputLayout.layouts[1].stride = 2 * sizeof(float); + inputLayout.layouts[2].stride = 3 * sizeof(float); + + MTLRenderPipelineDescriptor *rpDesc = [[MTLRenderPipelineDescriptor alloc] init]; + rpDesc.vertexDescriptor = inputLayout; + + rpDesc.vertexFunction = g.vs.first; + rpDesc.fragmentFunction = g.fs.first; + + rpDesc.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm; + rpDesc.colorAttachments[0].blendingEnabled = true; + rpDesc.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorOne; + rpDesc.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne; + rpDesc.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + rpDesc.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + + if (g.dev.depth24Stencil8PixelFormatSupported) { + rpDesc.depthAttachmentPixelFormat = MTLPixelFormatDepth24Unorm_Stencil8; + rpDesc.stencilAttachmentPixelFormat = MTLPixelFormatDepth24Unorm_Stencil8; + } else { + rpDesc.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8; + rpDesc.stencilAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8; + } + + NSError *err = nil; + g.pipeline = [g.dev newRenderPipelineStateWithDescriptor: rpDesc error: &err]; + if (!g.pipeline) { + const QString msg = QString::fromNSString(err.localizedDescription); + qFatal("Failed to create render pipeline state: %s", qPrintable(msg)); + } + [rpDesc release]; + } +} + +MetalRenderNode::MetalRenderNode(QQuickItem *item) + : m_item(item) +{ + g.vs.first = g.fs.first = nil; + g.vs.second = g.fs.second = nil; + + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) { + g.vbuf[i] = nil; + g.ubuf[i] = nil; + } +} + +MetalRenderNode::~MetalRenderNode() +{ + releaseResources(); +} + +void MetalRenderNode::releaseResources() +{ + [g.stencilEnabledDsState release]; + g.stencilEnabledDsState = nil; + + [g.pipeline release]; + g.pipeline = nil; + + [g.vs.first release]; + [g.vs.second release]; + + [g.fs.first release]; + [g.fs.second release]; + + g.vs.first = g.fs.first = nil; + g.vs.second = g.fs.second = nil; + + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) { + [g.vbuf[i] release]; + g.vbuf[i] = nil; + [g.ubuf[i] release]; + g.ubuf[i] = nil; + } +} + +void MetalRenderNode::render(const RenderState *state) +{ + QQuickWindow *window = m_item->window(); + const QQuickWindow::GraphicsStateInfo *stateInfo = window->graphicsStateInfo(); + id vbuf = g.vbuf[stateInfo->currentFrameSlot]; + id ubuf = g.ubuf[stateInfo->currentFrameSlot]; + + QPointF p0(m_item->width() - 1, m_item->height() - 1); + QPointF p1(0, 0); + QPointF p2(0, m_item->height() - 1); + + float vertices[6] = { float(p0.x()), float(p0.y()), + float(p1.x()), float(p1.y()), + float(p2.x()), float(p2.y()) }; + char *p = (char *) [vbuf contents]; + memcpy(p, vertices, VERTEX_SIZE); + + const QMatrix4x4 mvp = *state->projectionMatrix() * *matrix(); + const float opacity = inheritedOpacity(); + + p = (char *) [ubuf contents]; + memcpy(p, mvp.constData(), 64); + memcpy(p + 64, &opacity, 4); + + QSGRendererInterface *rif = window->rendererInterface(); + id encoder = (id) rif->getResource( + window, QSGRendererInterface::CommandEncoderResource); + Q_ASSERT(encoder); + + [encoder setVertexBuffer: vbuf offset: 0 atIndex: 1]; + [encoder setVertexBuffer: vbuf offset: VERTEX_SIZE atIndex: 2]; + + [encoder setVertexBuffer: ubuf offset: 0 atIndex: 0]; + [encoder setFragmentBuffer: ubuf offset: 0 atIndex: 0]; + + // Clip support. + if (state->scissorEnabled()) { + const QRect r = state->scissorRect(); // bottom-up + MTLScissorRect s; + s.x = r.x(); + s.y = (window->height() * window->effectiveDevicePixelRatio()) - (r.y() + r.height()); + s.width = r.width(); + s.height = r.height(); + [encoder setScissorRect: s]; + } + if (state->stencilEnabled()) { + [encoder setDepthStencilState: g.stencilEnabledDsState]; + [encoder setStencilReferenceValue: state->stencilValue()]; + } + + [encoder setRenderPipelineState: g.pipeline]; + [encoder drawPrimitives: MTLPrimitiveTypeTriangle vertexStart: 0 vertexCount: 3 instanceCount: 1 baseInstance: 0]; +} + +QSGRenderNode::StateFlags MetalRenderNode::changedStates() const +{ + return BlendState | ScissorState | StencilState; +} + +QSGRenderNode::RenderingFlags MetalRenderNode::flags() const +{ + return BoundedRectRendering | DepthAwareRendering; +} + +QRectF MetalRenderNode::rect() const +{ + return QRect(0, 0, m_item->width(), m_item->height()); +} diff --git a/examples/quick/scenegraph/rendernode/metalshader.frag b/examples/quick/scenegraph/rendernode/metalshader.frag new file mode 100644 index 0000000000..907faa537f --- /dev/null +++ b/examples/quick/scenegraph/rendernode/metalshader.frag @@ -0,0 +1,28 @@ +#include +#include + +using namespace metal; + +struct buf +{ + float4x4 matrix; + float opacity; +}; + +struct main0_out +{ + float4 fragColor [[color(0)]]; +}; + +struct main0_in +{ + float4 v_color [[user(locn0)]]; +}; + +fragment main0_out main0(main0_in in [[stage_in]], constant buf& ubuf [[buffer(0)]]) +{ + main0_out out = {}; + out.fragColor = in.v_color * ubuf.opacity; + return out; +} + diff --git a/examples/quick/scenegraph/rendernode/metalshader.vert b/examples/quick/scenegraph/rendernode/metalshader.vert new file mode 100644 index 0000000000..12b721f524 --- /dev/null +++ b/examples/quick/scenegraph/rendernode/metalshader.vert @@ -0,0 +1,31 @@ +#include +#include + +using namespace metal; + +struct buf +{ + float4x4 matrix; + float opacity; +}; + +struct main0_out +{ + float4 v_color [[user(locn0)]]; + float4 gl_Position [[position]]; +}; + +struct main0_in +{ + float4 pos [[attribute(0)]]; + float4 color [[attribute(1)]]; +}; + +vertex main0_out main0(main0_in in [[stage_in]], constant buf& ubuf [[buffer(0)]]) +{ + main0_out out = {}; + out.v_color = in.color; + out.gl_Position = ubuf.matrix * in.pos; + return out; +} + diff --git a/examples/quick/scenegraph/rendernode/rendernode.pro b/examples/quick/scenegraph/rendernode/rendernode.pro index 76e498042b..897b0b1f08 100644 --- a/examples/quick/scenegraph/rendernode/rendernode.pro +++ b/examples/quick/scenegraph/rendernode/rendernode.pro @@ -22,3 +22,9 @@ qtConfig(d3d12) { SOURCES += d3d12renderer.cpp LIBS += -ld3d12 } + +macos { + HEADERS += metalrenderer.h + SOURCES += metalrenderer.mm + LIBS += -framework Metal -framework AppKit +} diff --git a/examples/quick/scenegraph/rendernode/rendernode.qrc b/examples/quick/scenegraph/rendernode/rendernode.qrc index 049adcf8a6..5907eab62c 100644 --- a/examples/quick/scenegraph/rendernode/rendernode.qrc +++ b/examples/quick/scenegraph/rendernode/rendernode.qrc @@ -3,5 +3,7 @@ main.qml shader_vert.cso shader_frag.cso + metalshader.vert + metalshader.frag -- cgit v1.2.3 From 5d995ae122aa07486ead849560b74d2b62b883bb Mon Sep 17 00:00:00 2001 From: Shawn Rutledge Date: Thu, 3 Dec 2015 17:48:38 +0100 Subject: Move currentFrame and frameCount properties up to QQuickImageBase AnimatedImage already had these properties, but some typically non-animated image formats such as PDF, TIFF and ICO can also support multiple pages. In either case, the currentFrame property can be used to select a specific frame or page. However an AnimatedImage uses a QMovie to do that, whereas a plain Image uses QQuickPixmap. So the accessors need to be virtual in order to have these different implementations. [ChangeLog][QtQuick][Image] Image and BorderImage now have currentFrame and frameCount properties which can be used to step through the frames of multi-page image formats such as TIFF, WEBP and ICO. Task-number: QTBUG-77506 Change-Id: Id4d95a99a26a862957e44b1bd8ffe06d7eababef Reviewed-by: Eirik Aavitsland --- examples/quick/imageelements/content/multi.ico | Bin 0 -> 27110 bytes examples/quick/imageelements/framestepping.qml | 82 ++++++++++++++++++++ examples/quick/imageelements/imageelements.qml | 2 + examples/quick/imageelements/imageelements.qrc | 3 + .../quick/imageelements/multiframeborderimage.qml | 83 +++++++++++++++++++++ 5 files changed, 170 insertions(+) create mode 100644 examples/quick/imageelements/content/multi.ico create mode 100644 examples/quick/imageelements/framestepping.qml create mode 100644 examples/quick/imageelements/multiframeborderimage.qml (limited to 'examples/quick') diff --git a/examples/quick/imageelements/content/multi.ico b/examples/quick/imageelements/content/multi.ico new file mode 100644 index 0000000000..b748ceaa29 Binary files /dev/null and b/examples/quick/imageelements/content/multi.ico differ diff --git a/examples/quick/imageelements/framestepping.qml b/examples/quick/imageelements/framestepping.qml new file mode 100644 index 0000000000..f5bad46e7b --- /dev/null +++ b/examples/quick/imageelements/framestepping.qml @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.14 + +Rectangle { + width: 480 + height: 320 + Image { + id: img + anchors.centerIn: parent + cache: true + source: "content/multi.ico" + + Shortcut { + sequence: StandardKey.MoveToNextPage + enabled: img.currentFrame < img.frameCount - 1 + onActivated: img.currentFrame++ + } + Shortcut { + sequence: StandardKey.MoveToPreviousPage + enabled: img.currentFrame > 0 + onActivated: img.currentFrame-- + } + } + + Text { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.margins: 6 + horizontalAlignment: Text.AlignHCenter + text: "frame " + (img.currentFrame + 1) + " of " + img.frameCount + + "\nPress PgUp/PgDn to switch frames" + } +} diff --git a/examples/quick/imageelements/imageelements.qml b/examples/quick/imageelements/imageelements.qml index dfb4d24ea6..91f2e034f7 100644 --- a/examples/quick/imageelements/imageelements.qml +++ b/examples/quick/imageelements/imageelements.qml @@ -64,6 +64,8 @@ Item { addExample("AnimatedImage", "An image which plays animated formats", Qt.resolvedUrl("animatedimage.qml")); addExample("AnimatedSprite", "A simple sprite-based animation", Qt.resolvedUrl("animatedsprite.qml")); addExample("SpriteSequence", "A sprite-based animation with complex transitions", Qt.resolvedUrl("spritesequence.qml")); + addExample("FrameStepping", "A multi-frame non-animated image", Qt.resolvedUrl("framestepping.qml")); + addExample("MultiBorderImage", "A multi-frame image with scaled borders", Qt.resolvedUrl("multiframeborderimage.qml")); } } } diff --git a/examples/quick/imageelements/imageelements.qrc b/examples/quick/imageelements/imageelements.qrc index 2488fb083b..cedef2204c 100644 --- a/examples/quick/imageelements/imageelements.qrc +++ b/examples/quick/imageelements/imageelements.qrc @@ -8,6 +8,7 @@ content/colors-stretch.sci content/colors.png content/ImageCell.qml + content/multi.ico content/MyBorderImage.qml content/qt-logo.png content/shadow.png @@ -18,6 +19,8 @@ animatedimage.qml animatedsprite.qml borderimage.qml + framestepping.qml + multiframeborderimage.qml image.qml shadows.qml spritesequence.qml diff --git a/examples/quick/imageelements/multiframeborderimage.qml b/examples/quick/imageelements/multiframeborderimage.qml new file mode 100644 index 0000000000..0805ea4243 --- /dev/null +++ b/examples/quick/imageelements/multiframeborderimage.qml @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.14 + +Rectangle { + width: 480 + height: 320 + BorderImage { + id: img + anchors.fill: parent + anchors.margins: 6 + cache: true + source: "content/multi.ico" + border { left: 19; top: 19; right: 19; bottom: 19 } + horizontalTileMode: BorderImage.Stretch + + Shortcut { + sequence: StandardKey.MoveToNextPage + enabled: img.currentFrame < img.frameCount - 1 + onActivated: img.currentFrame++ + } + Shortcut { + sequence: StandardKey.MoveToPreviousPage + enabled: img.currentFrame > 0 + onActivated: img.currentFrame-- + } + } + + Text { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: "frame " + (img.currentFrame + 1) + " of " + img.frameCount + + "\nPress PgUp/PgDn to switch frames" + } +} -- cgit v1.2.3