diff options
Diffstat (limited to 'examples/quick/scenegraph')
11 files changed, 878 insertions, 1 deletions
diff --git a/examples/quick/scenegraph/metaltextureimport/doc/images/metaltextureimport-example.jpg b/examples/quick/scenegraph/metaltextureimport/doc/images/metaltextureimport-example.jpg Binary files differnew file mode 100644 index 0000000000..19ad40cd85 --- /dev/null +++ b/examples/quick/scenegraph/metaltextureimport/doc/images/metaltextureimport-example.jpg diff --git a/examples/quick/scenegraph/metaltextureimport/doc/src/metaltextureimport.qdoc b/examples/quick/scenegraph/metaltextureimport/doc/src/metaltextureimport.qdoc new file mode 100644 index 0000000000..2a584c26cc --- /dev/null +++ b/examples/quick/scenegraph/metaltextureimport/doc/src/metaltextureimport.qdoc @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** 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/metaltextureimport + \title Scene Graph - Metal Texture Import + \ingroup qtquickexamples + \brief Shows how to use a texture created directly with Metal + + \image metaltextureimport-example.jpg + + The Metal Texture Import example shows how an application can import and + use a + \l{https://developer.apple.com/documentation/metal/mtltexture}{MTLTexture} + in the Qt Quick scene. This provides an alternative to the \l{Scene Graph - + Metal Under QML}{underlay}, overlay, or \l{Scene Graph - Custom Rendering + with QSGRenderNode}{render node} approaches when it comes to integrating + native Metal rendering. In many cases going through a texture, and + therefore "flattening" the 3D contents first, is the best option to + integrate and mix custom 3D contents with the 2D UI elements provided by Qt + Quick. + + \snippet scenegraph/metaltextureimport/main.qml 1 + \snippet scenegraph/metaltextureimport/main.qml 2 + + The application exposes a custom QQuickItem subclass under ther name of + CustomTextureItem. This is instantiated in QML. The value of the \c t + property is animated as well. + + \snippet scenegraph/metaltextureimport/metaltextureimport.h 1 + + The implementation of our custom item involves overriding + QQuickItem::updatePaintNode(), as well as functions and slots related to + geometry changes and cleanup. + + \snippet scenegraph/metaltextureimport/metaltextureimport.mm 1 + + We also need a scenegraph node. Instead of deriving directly from QSGNode, + we can use QSGSimpleTextureNode which gives us some of the functionality + pre-implemented as a convenience. + + \snippet scenegraph/metaltextureimport/metaltextureimport.mm 2 + + The updatePaintNode() function of the item is called on the render thread + (if there is one), with the main (gui) thread blocked. Here we create a new + node if there has not yet been one, and update it. Accessing Qt objects + living on the main thread is safe here, so sync() will calculate and copy + the values it needs from QQuickItem or QQuickWindow. + + \snippet scenegraph/metaltextureimport/metaltextureimport.mm 3 + + The node does not merely rely on the typical QQuickItem - QSGNode + update sequence, it connects to QQuickWindow::beforeRendering() as + well. That is where the contents of the Metal texture will be updated by + encoding a full render pass, targeting the texture, on the Qt Quicks + scenegraph's command buffer. beforeRendering() is the right place for this, + because the signal is emitted before Qt Quick starts to encode its own + rendering commands. Choosing QQuickWindow::beforeRenderPassRecording() + instead would be an error in this exanple. + + \snippet scenegraph/metaltextureimport/metaltextureimport.mm 4 + \snippet scenegraph/metaltextureimport/metaltextureimport.mm 5 + + After copying the values we need, sync() also performs some graphics + resource initialization. The MTLDevice is queried from the scenegraph. Once + a MTLTexture is available, a QSGTexture wrapping (not owning) it is created + via QQuickWindow::createTextureFromNativeObject(). This function is a + modern equivalent to QQuickWindow::createTextureFromId() that is not tied + to OpenGL. Finally, the QSGTexture is associated with the underlying + materials by calling the base class' setTexture() function. + + \snippet scenegraph/metaltextureimport/metaltextureimport.mm 6 + + render(), the slot connected to beforeRendering(), encodes the rendering + commands using the buffers and pipeline state objects created in sync(). + + */ diff --git a/examples/quick/scenegraph/metaltextureimport/main.cpp b/examples/quick/scenegraph/metaltextureimport/main.cpp new file mode 100644 index 0000000000..c969817e8f --- /dev/null +++ b/examples/quick/scenegraph/metaltextureimport/main.cpp @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the demonstration applications 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 <QGuiApplication> +#include <QtQuick/QQuickView> +#include "metaltextureimport.h" + +int main(int argc, char **argv) +{ + QGuiApplication app(argc, argv); + + qmlRegisterType<CustomTextureItem>("MetalTextureImport", 1, 0, "CustomTextureItem"); + + QQuickWindow::setSceneGraphBackend(QSGRendererInterface::MetalRhi); + + QQuickView view; + view.setResizeMode(QQuickView::SizeRootObjectToView); + view.setSource(QUrl("qrc:///scenegraph/metaltextureimport/main.qml")); + view.resize(400, 400); + view.show(); + + return app.exec(); +} diff --git a/examples/quick/scenegraph/metaltextureimport/main.qml b/examples/quick/scenegraph/metaltextureimport/main.qml new file mode 100644 index 0000000000..facab4e440 --- /dev/null +++ b/examples/quick/scenegraph/metaltextureimport/main.qml @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the demonstration applications 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.0 +//! [1] +import MetalTextureImport 1.0 +//! [1] + +Rectangle { + gradient: Gradient { + GradientStop { position: 0; color: "steelblue" } + GradientStop { position: 1; color: "black" } + } + + //! [2] + CustomTextureItem { + id: renderer + anchors.fill: parent + anchors.margins: 10 + + SequentialAnimation on t { + NumberAnimation { to: 1; duration: 2500; easing.type: Easing.InQuad } + NumberAnimation { to: 0; duration: 2500; easing.type: Easing.OutQuad } + loops: Animation.Infinite + running: true + } + //! [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 { + PauseAnimation { duration: 5000 } + ParallelAnimation { + NumberAnimation { target: scale; property: "xScale"; to: 0.6; duration: 1000; easing.type: Easing.InOutBack } + NumberAnimation { target: scale; property: "yScale"; to: 0.6; duration: 1000; easing.type: Easing.InOutBack } + } + NumberAnimation { target: rotation; property: "angle"; to: 80; duration: 1000; easing.type: Easing.InOutCubic } + NumberAnimation { target: rotation; property: "angle"; to: -80; duration: 1000; easing.type: Easing.InOutCubic } + NumberAnimation { target: rotation; property: "angle"; to: 0; duration: 1000; easing.type: Easing.InOutCubic } + NumberAnimation { target: renderer; property: "opacity"; to: 0.1; duration: 1000; easing.type: Easing.InOutCubic } + PauseAnimation { duration: 1000 } + NumberAnimation { target: renderer; property: "opacity"; to: 1.0; duration: 1000; easing.type: Easing.InOutCubic } + ParallelAnimation { + NumberAnimation { target: scale; property: "xScale"; to: 1; duration: 1000; easing.type: Easing.InOutBack } + NumberAnimation { target: scale; property: "yScale"; to: 1; duration: 1000; easing.type: Easing.InOutBack } + } + running: true + loops: Animation.Infinite + } + + Rectangle { + id: labelFrame + anchors.margins: -10 + radius: 5 + color: "white" + border.color: "black" + opacity: 0.5 + anchors.fill: label + } + + Text { + id: label + anchors.bottom: renderer.bottom + anchors.left: renderer.left + anchors.right: renderer.right + anchors.margins: 20 + wrapMode: Text.WordWrap + text: "The squircle, using rendering code borrowed from the metalunderqml example, is rendered into a texture directly with Metal. The MTLTexture is then imported and used in a custom Qt Quick item." + } +} diff --git a/examples/quick/scenegraph/metaltextureimport/metaltextureimport.h b/examples/quick/scenegraph/metaltextureimport/metaltextureimport.h new file mode 100644 index 0000000000..afc5aced97 --- /dev/null +++ b/examples/quick/scenegraph/metaltextureimport/metaltextureimport.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the demonstration applications 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 METALTEXTUREIMPORT_H +#define METALTEXTUREIMPORT_H + +#include <QtQuick/QQuickItem> + +class CustomTextureNode; + +//! [1] +class CustomTextureItem : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(qreal t READ t WRITE setT NOTIFY tChanged) + +public: + CustomTextureItem(); + + qreal t() const { return m_t; } + void setT(qreal t); + +signals: + void tChanged(); + +protected: + QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *) override; + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override; + +private slots: + void invalidateSceneGraph(); + +private: + void releaseResources() override; + + CustomTextureNode *m_node = nullptr; + qreal m_t = 0; +}; +//! [1] + +#endif // METALTEXTUREIMPORT_H diff --git a/examples/quick/scenegraph/metaltextureimport/metaltextureimport.mm b/examples/quick/scenegraph/metaltextureimport/metaltextureimport.mm new file mode 100644 index 0000000000..6bb68dac44 --- /dev/null +++ b/examples/quick/scenegraph/metaltextureimport/metaltextureimport.mm @@ -0,0 +1,424 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the demonstration applications 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 "metaltextureimport.h" +#include <QtGui/QScreen> +#include <QtQuick/QQuickWindow> +#include <QtQuick/QSGTextureProvider> +#include <QtQuick/QSGSimpleTextureNode> + +#include <Metal/Metal.h> + +//! [1] +class CustomTextureNode : public QSGTextureProvider, public QSGSimpleTextureNode +{ + Q_OBJECT + +public: + CustomTextureNode(QQuickItem *item); + ~CustomTextureNode(); + + QSGTexture *texture() const override; + + void sync(); +//! [1] +private slots: + void render(); + +private: + enum Stage { + VertexStage, + FragmentStage + }; + void prepareShader(Stage stage); + using FuncAndLib = QPair<id<MTLFunction>, id<MTLLibrary> >; + FuncAndLib compileShaderFromSource(const QByteArray &src, const QByteArray &entryPoint); + + QQuickItem *m_item; + QQuickWindow *m_window; + QSize m_size; + qreal m_dpr; + id<MTLDevice> m_device = nil; + id<MTLTexture> m_texture = nil; + + bool m_initialized = false; + QByteArray m_vert; + QByteArray m_vertEntryPoint; + QByteArray m_frag; + QByteArray m_fragEntryPoint; + FuncAndLib m_vs; + FuncAndLib m_fs; + id<MTLBuffer> m_vbuf; + id<MTLBuffer> m_ubuf[3]; + id<MTLRenderPipelineState> m_pipeline; + + float m_t; +}; + +CustomTextureItem::CustomTextureItem() +{ + setFlag(ItemHasContents, true); +} + +// The beauty of using a true QSGNode: no need for complicated cleanup +// arrangements, unlike in other examples like metalunderqml, because the +// scenegraph will handle destroying the node at the appropriate time. + +void CustomTextureItem::invalidateSceneGraph() // called on the render thread when the scenegraph is invalidated +{ + m_node = nullptr; +} + +void CustomTextureItem::releaseResources() // called on the gui thread if the item is removed from scene +{ + m_node = nullptr; +} + +//! [2] +QSGNode *CustomTextureItem::updatePaintNode(QSGNode *node, UpdatePaintNodeData *) +{ + CustomTextureNode *n = static_cast<CustomTextureNode *>(node); + + if (!n && (width() <= 0 || height() <= 0)) + return nullptr; + + if (!n) { + m_node = new CustomTextureNode(this); + n = m_node; + } + + m_node->sync(); + + n->setTextureCoordinatesTransform(QSGSimpleTextureNode::NoTransform); + n->setFiltering(QSGTexture::Linear); + n->setRect(0, 0, width(), height()); + + window()->update(); // ensure getting to beforeRendering() at some point + + return n; +} +//! [2] + +void CustomTextureItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + QQuickItem::geometryChanged(newGeometry, oldGeometry); + + if (newGeometry.size() != oldGeometry.size()) + update(); +} + +void CustomTextureItem::setT(qreal t) +{ + if (t == m_t) + return; + + m_t = t; + emit tChanged(); + + update(); +} + +//! [3] +CustomTextureNode::CustomTextureNode(QQuickItem *item) + : m_item(item) +{ + m_window = m_item->window(); + connect(m_window, &QQuickWindow::beforeRendering, this, &CustomTextureNode::render); + connect(m_window, &QQuickWindow::screenChanged, this, [this]() { + if (m_window->effectiveDevicePixelRatio() != m_dpr) + m_item->update(); + }); +//! [3] + m_vs.first = nil; + m_vs.second = nil; + + m_fs.first = nil; + m_fs.second = nil; + + m_vbuf = nil; + + for (int i = 0; i < 3; ++i) + m_ubuf[i] = nil; + + m_pipeline = nil; + + qDebug("renderer created"); +} + +CustomTextureNode::~CustomTextureNode() +{ + [m_pipeline release]; + + [m_vbuf release]; + + for (int i = 0; i < 3; ++i) + [m_ubuf[i] release]; + + [m_vs.first release]; + [m_vs.second release]; + + [m_fs.first release]; + [m_fs.second release]; + + delete texture(); + [m_texture release]; + + qDebug("renderer destroyed"); +} + +QSGTexture *CustomTextureNode::texture() const +{ + return QSGSimpleTextureNode::texture(); +} + +static const float vertices[] = { + -1, -1, + 1, -1, + -1, 1, + 1, 1 +}; + +const int UBUF_SIZE = 4; + +//! [4] +void CustomTextureNode::sync() +{ + m_dpr = m_window->effectiveDevicePixelRatio(); + const QSize newSize = m_window->size() * m_dpr; + bool needsNew = false; + + if (!texture()) + needsNew = true; + + if (newSize != m_size) { + needsNew = true; + m_size = newSize; + } + + if (needsNew) { + delete texture(); + [m_texture release]; + + QSGRendererInterface *rif = m_window->rendererInterface(); + m_device = (id<MTLDevice>) rif->getResource(m_window, QSGRendererInterface::DeviceResource); + Q_ASSERT(m_device); + + MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init]; + desc.textureType = MTLTextureType2D; + desc.pixelFormat = MTLPixelFormatRGBA8Unorm; + desc.width = m_size.width(); + desc.height = m_size.height(); + desc.mipmapLevelCount = 1; + desc.resourceOptions = MTLResourceStorageModePrivate; + desc.storageMode = MTLStorageModePrivate; + desc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget; + m_texture = [m_device newTextureWithDescriptor: desc]; + [desc release]; + + QSGTexture *wrapper = m_window->createTextureFromNativeObject(QQuickWindow::NativeObjectTexture, + &m_texture, + 0, + m_size); + + qDebug() << "Got QSGTexture wrapper" << wrapper << "for an MTLTexture of size" << m_size; + + setTexture(wrapper); + } +//! [4] + if (!m_initialized && texture()) { + m_initialized = true; + + prepareShader(VertexStage); + prepareShader(FragmentStage); + + m_vs = compileShaderFromSource(m_vert, m_vertEntryPoint); + m_fs = compileShaderFromSource(m_frag, m_fragEntryPoint); + + const int framesInFlight = m_window->graphicsStateInfo()->framesInFlight; + + m_vbuf = [m_device newBufferWithLength: sizeof(vertices) options: MTLResourceStorageModeShared]; + void *p = [m_vbuf contents]; + memcpy(p, vertices, sizeof(vertices)); + + for (int i = 0; i < framesInFlight; ++i) + m_ubuf[i] = [m_device newBufferWithLength: UBUF_SIZE options: MTLResourceStorageModeShared]; + + 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 + inputLayout.layouts[1].stride = 2 * sizeof(float); + + MTLRenderPipelineDescriptor *rpDesc = [[MTLRenderPipelineDescriptor alloc] init]; + rpDesc.vertexDescriptor = inputLayout; + + rpDesc.vertexFunction = m_vs.first; + rpDesc.fragmentFunction = m_fs.first; + + rpDesc.colorAttachments[0].pixelFormat = MTLPixelFormatRGBA8Unorm; + rpDesc.colorAttachments[0].blendingEnabled = true; + rpDesc.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha; + rpDesc.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorSourceAlpha; + rpDesc.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOne; + rpDesc.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOne; + + NSError *err = nil; + m_pipeline = [m_device newRenderPipelineStateWithDescriptor: rpDesc error: &err]; + if (!m_pipeline) { + const QString msg = QString::fromNSString(err.localizedDescription); + qFatal("Failed to create render pipeline state: %s", qPrintable(msg)); + } + [rpDesc release]; + + qDebug("resources initialized"); + } + +//! [5] + m_t = float(static_cast<CustomTextureItem *>(m_item)->t()); +//! [5] +} + +// This is hooked up to beforeRendering() so we can start our own render +// command encoder. If we instead wanted to use the scenegraph's render command +// encoder (targeting the window), it should be connected to +// beforeRenderPassRecording() instead. +//! [6] +void CustomTextureNode::render() +{ + if (!m_initialized) + return; + + // Render to m_texture. + MTLRenderPassDescriptor *renderpassdesc = [MTLRenderPassDescriptor renderPassDescriptor]; + MTLClearColor c = MTLClearColorMake(0, 0, 0, 1); + renderpassdesc.colorAttachments[0].loadAction = MTLLoadActionClear; + renderpassdesc.colorAttachments[0].storeAction = MTLStoreActionStore; + renderpassdesc.colorAttachments[0].clearColor = c; + renderpassdesc.colorAttachments[0].texture = m_texture; + + QSGRendererInterface *rif = m_window->rendererInterface(); + id<MTLCommandBuffer> cb = (id<MTLCommandBuffer>) rif->getResource(m_window, QSGRendererInterface::CommandListResource); + Q_ASSERT(cb); + id<MTLRenderCommandEncoder> encoder = [cb renderCommandEncoderWithDescriptor: renderpassdesc]; + + const QQuickWindow::GraphicsStateInfo *stateInfo = m_window->graphicsStateInfo(); + void *p = [m_ubuf[stateInfo->currentFrameSlot] contents]; + memcpy(p, &m_t, 4); + + MTLViewport vp; + vp.originX = 0; + vp.originY = 0; + vp.width = m_size.width(); + vp.height = m_size.height(); + vp.znear = 0; + vp.zfar = 1; + [encoder setViewport: vp]; + + [encoder setFragmentBuffer: m_ubuf[stateInfo->currentFrameSlot] offset: 0 atIndex: 0]; + [encoder setVertexBuffer: m_vbuf offset: 0 atIndex: 1]; + [encoder setRenderPipelineState: m_pipeline]; + [encoder drawPrimitives: MTLPrimitiveTypeTriangleStrip vertexStart: 0 vertexCount: 4 instanceCount: 1 baseInstance: 0]; + + [encoder endEncoding]; +} +//! [6] + +void CustomTextureNode::prepareShader(Stage stage) +{ + QString filename; + if (stage == VertexStage) { + filename = QLatin1String(":/scenegraph/metaltextureimport/squircle.vert"); + } else { + Q_ASSERT(stage == FragmentStage); + filename = QLatin1String(":/scenegraph/metaltextureimport/squircle.frag"); + } + QFile f(filename); + if (!f.open(QIODevice::ReadOnly)) + qFatal("Failed to read shader %s", qPrintable(filename)); + + const QByteArray contents = f.readAll(); + + if (stage == VertexStage) { + m_vert = contents; + Q_ASSERT(!m_vert.isEmpty()); + m_vertEntryPoint = QByteArrayLiteral("main0"); + } else { + m_frag = contents; + Q_ASSERT(!m_frag.isEmpty()); + m_fragEntryPoint = QByteArrayLiteral("main0"); + } +} + +CustomTextureNode::FuncAndLib CustomTextureNode::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 = [m_device 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; +} + +#include "metaltextureimport.moc" diff --git a/examples/quick/scenegraph/metaltextureimport/metaltextureimport.pro b/examples/quick/scenegraph/metaltextureimport/metaltextureimport.pro new file mode 100644 index 0000000000..c8ea7ce478 --- /dev/null +++ b/examples/quick/scenegraph/metaltextureimport/metaltextureimport.pro @@ -0,0 +1,10 @@ +QT += qml quick + +HEADERS += metaltextureimport.h +SOURCES += metaltextureimport.mm main.cpp +RESOURCES += metaltextureimport.qrc + +LIBS += -framework Metal -framework AppKit + +target.path = $$[QT_INSTALL_EXAMPLES]/quick/scenegraph/metaltextureimport +INSTALLS += target diff --git a/examples/quick/scenegraph/metaltextureimport/metaltextureimport.qrc b/examples/quick/scenegraph/metaltextureimport/metaltextureimport.qrc new file mode 100644 index 0000000000..7a8dd7a860 --- /dev/null +++ b/examples/quick/scenegraph/metaltextureimport/metaltextureimport.qrc @@ -0,0 +1,7 @@ +<RCC> + <qresource prefix="/scenegraph/metaltextureimport"> + <file>main.qml</file> + <file>squircle.vert</file> + <file>squircle.frag</file> + </qresource> +</RCC> diff --git a/examples/quick/scenegraph/metaltextureimport/squircle.frag b/examples/quick/scenegraph/metaltextureimport/squircle.frag new file mode 100644 index 0000000000..15f34624fe --- /dev/null +++ b/examples/quick/scenegraph/metaltextureimport/squircle.frag @@ -0,0 +1,29 @@ +#include <metal_stdlib> +#include <simd/simd.h> + +using namespace metal; + +struct buf +{ + float t; +}; + +struct main0_out +{ + float4 fragColor [[color(0)]]; +}; + +struct main0_in +{ + float2 coords [[user(locn0)]]; +}; + +fragment main0_out main0(main0_in in [[stage_in]], constant buf& ubuf [[buffer(0)]]) +{ + main0_out out = {}; + float i = 1.0 - (pow(abs(in.coords.x), 4.0) + pow(abs(in.coords.y), 4.0)); + i = smoothstep(ubuf.t - 0.800000011920928955078125, ubuf.t + 0.800000011920928955078125, i); + i = floor(i * 20.0) / 20.0; + out.fragColor = float4((in.coords * 0.5) + float2(0.5), i, i); + return out; +} diff --git a/examples/quick/scenegraph/metaltextureimport/squircle.vert b/examples/quick/scenegraph/metaltextureimport/squircle.vert new file mode 100644 index 0000000000..a88c59f541 --- /dev/null +++ b/examples/quick/scenegraph/metaltextureimport/squircle.vert @@ -0,0 +1,23 @@ +#include <metal_stdlib> +#include <simd/simd.h> + +using namespace metal; + +struct main0_out +{ + float2 coords [[user(locn0)]]; + float4 gl_Position [[position]]; +}; + +struct main0_in +{ + float4 vertices [[attribute(0)]]; +}; + +vertex main0_out main0(main0_in in [[stage_in]]) +{ + main0_out out = {}; + out.gl_Position = in.vertices; + out.coords = in.vertices.xy; + return out; +} diff --git a/examples/quick/scenegraph/scenegraph.pro b/examples/quick/scenegraph/scenegraph.pro index b983301323..e05e1ddb44 100644 --- a/examples/quick/scenegraph/scenegraph.pro +++ b/examples/quick/scenegraph/scenegraph.pro @@ -17,7 +17,9 @@ SUBDIRS += \ threadedanimation macos { - SUBDIRS += metalunderqml + SUBDIRS += \ + metalunderqml \ + metaltextureimport } win32 { |