From 1a5c6544c88d6092ca57e780e332a0f302041b06 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Fri, 30 Aug 2019 12:52:57 +0200 Subject: Add metaltextureimport example Change-Id: I499c21083faf6096a0dc3b8ee5682dd5cbcd3ce7 Reviewed-by: Andy Nichols --- .../metaltextureimport/metaltextureimport.mm | 424 +++++++++++++++++++++ 1 file changed, 424 insertions(+) create mode 100644 examples/quick/scenegraph/metaltextureimport/metaltextureimport.mm (limited to 'examples/quick/scenegraph/metaltextureimport/metaltextureimport.mm') 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 +#include +#include +#include + +#include + +//! [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 >; + FuncAndLib compileShaderFromSource(const QByteArray &src, const QByteArray &entryPoint); + + QQuickItem *m_item; + QQuickWindow *m_window; + QSize m_size; + qreal m_dpr; + id m_device = nil; + id 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 m_vbuf; + id m_ubuf[3]; + id 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(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) 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(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 cb = (id) rif->getResource(m_window, QSGRendererInterface::CommandListResource); + Q_ASSERT(cb); + id 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" -- cgit v1.2.3