blob: 66a39083f715372505f05bb35055a2055317f224 [file] [log] [blame]
/****************************************************************************
**
** 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"