diff options
Diffstat (limited to 'examples/quick/scenegraph')
71 files changed, 4401 insertions, 121 deletions
diff --git a/examples/quick/scenegraph/d3d11underqml/d3d11squircle.cpp b/examples/quick/scenegraph/d3d11underqml/d3d11squircle.cpp new file mode 100644 index 0000000000..f05bf2f843 --- /dev/null +++ b/examples/quick/scenegraph/d3d11underqml/d3d11squircle.cpp @@ -0,0 +1,426 @@ +/**************************************************************************** +** +** 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 "d3d11squircle.h" +#include <QtCore/QRunnable> +#include <QtQuick/QQuickWindow> + +#include <d3d11.h> +#include <d3dcompiler.h> + +class SquircleRenderer : public QObject +{ + Q_OBJECT +public: + SquircleRenderer(); + ~SquircleRenderer(); + + void setT(qreal t) { m_t = t; } + void setViewportSize(const QSize &size) { m_viewportSize = size; } + void setWindow(QQuickWindow *window) { m_window = window; } + +public slots: + void frameStart(); + void mainPassRecordingStart(); + +private: + enum Stage { + VertexStage, + FragmentStage + }; + void prepareShader(Stage stage); + QByteArray compileShader(Stage stage, + const QByteArray &source, + const QByteArray &entryPoint); + void init(); + + QSize m_viewportSize; + qreal m_t; + QQuickWindow *m_window; + + ID3D11Device *m_device = nullptr; + ID3D11DeviceContext *m_context = nullptr; + QByteArray m_vert; + QByteArray m_vertEntryPoint; + QByteArray m_frag; + QByteArray m_fragEntryPoint; + + bool m_initialized = false; + ID3D11Buffer *m_vbuf = nullptr; + ID3D11Buffer *m_cbuf = nullptr; + ID3D11VertexShader *m_vs = nullptr; + ID3D11PixelShader *m_ps = nullptr; + ID3D11InputLayout *m_inputLayout = nullptr; + ID3D11RasterizerState *m_rastState = nullptr; + ID3D11DepthStencilState *m_dsState = nullptr; + ID3D11BlendState *m_blendState = nullptr; +}; + +D3D11Squircle::D3D11Squircle() + : m_t(0) + , m_renderer(nullptr) +{ + connect(this, &QQuickItem::windowChanged, this, &D3D11Squircle::handleWindowChanged); +} + +void D3D11Squircle::setT(qreal t) +{ + if (t == m_t) + return; + m_t = t; + emit tChanged(); + if (window()) + window()->update(); +} + +void D3D11Squircle::handleWindowChanged(QQuickWindow *win) +{ + if (win) { + connect(win, &QQuickWindow::beforeSynchronizing, this, &D3D11Squircle::sync, Qt::DirectConnection); + connect(win, &QQuickWindow::sceneGraphInvalidated, this, &D3D11Squircle::cleanup, Qt::DirectConnection); + + // Ensure we start with cleared to black. The squircle's blend mode relies on this. + win->setColor(Qt::black); + } +} + +SquircleRenderer::SquircleRenderer() + : m_t(0) +{ +} + +// The safe way to release custom graphics resources it to both connect to +// sceneGraphInvalidated() and implement releaseResources(). To support +// threaded render loops the latter performs the SquircleRenderer destruction +// via scheduleRenderJob(). Note that the D3D11Squircle may be gone by the time +// the QRunnable is invoked. + +void D3D11Squircle::cleanup() +{ + delete m_renderer; + m_renderer = nullptr; +} + +class CleanupJob : public QRunnable +{ +public: + CleanupJob(SquircleRenderer *renderer) : m_renderer(renderer) { } + void run() override { delete m_renderer; } +private: + SquircleRenderer *m_renderer; +}; + +void D3D11Squircle::releaseResources() +{ + window()->scheduleRenderJob(new CleanupJob(m_renderer), QQuickWindow::BeforeSynchronizingStage); + m_renderer = nullptr; +} + +SquircleRenderer::~SquircleRenderer() +{ + qDebug("cleanup"); + + if (m_vs) + m_vs->Release(); + + if (m_ps) + m_ps->Release(); + + if (m_vbuf) + m_vbuf->Release(); + + if (m_cbuf) + m_cbuf->Release(); + + if (m_inputLayout) + m_inputLayout->Release(); + + if (m_rastState) + m_rastState->Release(); + + if (m_dsState) + m_dsState->Release(); + + if (m_blendState) + m_blendState->Release(); +} + +void D3D11Squircle::sync() +{ + if (!m_renderer) { + m_renderer = new SquircleRenderer; + connect(window(), &QQuickWindow::beforeRendering, m_renderer, &SquircleRenderer::frameStart, Qt::DirectConnection); + connect(window(), &QQuickWindow::beforeRenderPassRecording, m_renderer, &SquircleRenderer::mainPassRecordingStart, Qt::DirectConnection); + } + m_renderer->setViewportSize(window()->size() * window()->devicePixelRatio()); + m_renderer->setT(m_t); + m_renderer->setWindow(window()); +} + +void SquircleRenderer::frameStart() +{ + QSGRendererInterface *rif = m_window->rendererInterface(); + + // We are not prepared for anything other than running with the RHI and its D3D11 backend. + Q_ASSERT(rif->graphicsApi() == QSGRendererInterface::Direct3D11Rhi); + + m_device = reinterpret_cast<ID3D11Device *>(rif->getResource(m_window, QSGRendererInterface::DeviceResource)); + Q_ASSERT(m_device); + m_context = reinterpret_cast<ID3D11DeviceContext *>(rif->getResource(m_window, QSGRendererInterface::DeviceContextResource)); + Q_ASSERT(m_context); + + if (m_vert.isEmpty()) + prepareShader(VertexStage); + if (m_frag.isEmpty()) + prepareShader(FragmentStage); + + if (!m_initialized) + init(); +} + +static const float vertices[] = { + -1, -1, + 1, -1, + -1, 1, + 1, 1 +}; + +void SquircleRenderer::mainPassRecordingStart() +{ + m_window->beginExternalCommands(); + + D3D11_MAPPED_SUBRESOURCE mp; + // will copy the entire constant buffer every time -> pass WRITE_DISCARD -> prevent pipeline stalls + HRESULT hr = m_context->Map(m_cbuf, 0, D3D11_MAP_WRITE_DISCARD, 0, &mp); + if (SUCCEEDED(hr)) { + float t = m_t; + memcpy(mp.pData, &t, 4); + m_context->Unmap(m_cbuf, 0); + } else { + qFatal("Failed to map constant buffer: 0x%x", hr); + } + + D3D11_VIEWPORT v; + v.TopLeftX = 0; + v.TopLeftY = 0; + v.Width = m_viewportSize.width(); + v.Height = m_viewportSize.height(); + v.MinDepth = 0; + v.MaxDepth = 1; + m_context->RSSetViewports(1, &v); + + m_context->VSSetShader(m_vs, nullptr, 0); + m_context->PSSetShader(m_ps, nullptr, 0); + m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + m_context->IASetInputLayout(m_inputLayout); + m_context->OMSetDepthStencilState(m_dsState, 0); + float blendConstants[] = { 1, 1, 1, 1 }; + m_context->OMSetBlendState(m_blendState, blendConstants, 0xFFFFFFFF); + m_context->RSSetState(m_rastState); + + const UINT stride = 2 * sizeof(float); // vec2 + const UINT offset = 0; + m_context->IASetVertexBuffers(0, 1, &m_vbuf, &stride, &offset); + m_context->PSSetConstantBuffers(0, 1, &m_cbuf); + + m_context->Draw(4, 0); + + m_window->endExternalCommands(); +} + +void SquircleRenderer::prepareShader(Stage stage) +{ + QString filename; + if (stage == VertexStage) { + filename = QLatin1String(":/scenegraph/d3d11underqml/squircle.vert"); + } else { + Q_ASSERT(stage == FragmentStage); + filename = QLatin1String(":/scenegraph/d3d11underqml/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("main"); + } else { + m_frag = contents; + Q_ASSERT(!m_frag.isEmpty()); + m_fragEntryPoint = QByteArrayLiteral("main"); + } +} + +QByteArray SquircleRenderer::compileShader(Stage stage, + const QByteArray &source, + const QByteArray &entryPoint) +{ + const char *target; + switch (stage) { + case VertexStage: + target = "vs_5_0"; + break; + case FragmentStage: + target = "ps_5_0"; + break; + default: + qFatal("Unknown shader stage %d", stage); + return QByteArray(); + } + + ID3DBlob *bytecode = nullptr; + ID3DBlob *errors = nullptr; + HRESULT hr = D3DCompile(source.constData(), source.size(), + nullptr, nullptr, nullptr, + entryPoint.constData(), target, 0, 0, &bytecode, &errors); + if (FAILED(hr) || !bytecode) { + qWarning("HLSL shader compilation failed: 0x%x", uint(hr)); + if (errors) { + const QByteArray msg(static_cast<const char *>(errors->GetBufferPointer()), + errors->GetBufferSize()); + errors->Release(); + qWarning("%s", msg.constData()); + } + return QByteArray(); + } + + QByteArray result; + result.resize(bytecode->GetBufferSize()); + memcpy(result.data(), bytecode->GetBufferPointer(), result.size()); + bytecode->Release(); + + return result; +} + +void SquircleRenderer::init() +{ + qDebug("init"); + m_initialized = true; + + const QByteArray vs = compileShader(VertexStage, m_vert, m_vertEntryPoint); + const QByteArray fs = compileShader(FragmentStage, m_frag, m_fragEntryPoint); + + HRESULT hr = m_device->CreateVertexShader(vs.constData(), vs.size(), nullptr, &m_vs); + if (FAILED(hr)) + qFatal("Failed to create vertex shader: 0x%x", hr); + + hr = m_device->CreatePixelShader(fs.constData(), fs.size(), nullptr, &m_ps); + if (FAILED(hr)) + qFatal("Failed to create pixel shader: 0x%x", hr); + + D3D11_BUFFER_DESC bufDesc; + memset(&bufDesc, 0, sizeof(bufDesc)); + bufDesc.ByteWidth = sizeof(vertices); + bufDesc.Usage = D3D11_USAGE_DEFAULT; + bufDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; + hr = m_device->CreateBuffer(&bufDesc, nullptr, &m_vbuf); + if (FAILED(hr)) + qFatal("Failed to create buffer: 0x%x", hr); + + m_context->UpdateSubresource(m_vbuf, 0, nullptr, vertices, 0, 0); + + bufDesc.ByteWidth = 256; + bufDesc.Usage = D3D11_USAGE_DYNAMIC; + bufDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + bufDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + hr = m_device->CreateBuffer(&bufDesc, nullptr, &m_cbuf); + if (FAILED(hr)) + qFatal("Failed to create buffer: 0x%x", hr); + + D3D11_INPUT_ELEMENT_DESC inputDesc; + memset(&inputDesc, 0, sizeof(inputDesc)); + // the output from SPIRV-Cross uses TEXCOORD<location> as the semantic + inputDesc.SemanticName = "TEXCOORD"; + inputDesc.SemanticIndex = 0; + inputDesc.Format = DXGI_FORMAT_R32G32_FLOAT; // vec2 + inputDesc.InputSlot = 0; + inputDesc.AlignedByteOffset = 0; + inputDesc.InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; + hr = m_device->CreateInputLayout(&inputDesc, 1, vs.constData(), vs.size(), &m_inputLayout); + if (FAILED(hr)) + qFatal("Failed to create input layout: 0x%x", hr); + + D3D11_RASTERIZER_DESC rastDesc; + memset(&rastDesc, 0, sizeof(rastDesc)); + rastDesc.FillMode = D3D11_FILL_SOLID; + rastDesc.CullMode = D3D11_CULL_NONE; + hr = m_device->CreateRasterizerState(&rastDesc, &m_rastState); + if (FAILED(hr)) + qFatal("Failed to create rasterizer state: 0x%x", hr); + + D3D11_DEPTH_STENCIL_DESC dsDesc; + memset(&dsDesc, 0, sizeof(dsDesc)); + hr = m_device->CreateDepthStencilState(&dsDesc, &m_dsState); + if (FAILED(hr)) + qFatal("Failed to create depth/stencil state: 0x%x", hr); + + D3D11_BLEND_DESC blendDesc; + memset(&blendDesc, 0, sizeof(blendDesc)); + blendDesc.IndependentBlendEnable = true; + D3D11_RENDER_TARGET_BLEND_DESC blend; + memset(&blend, 0, sizeof(blend)); + blend.BlendEnable = true; + blend.SrcBlend = D3D11_BLEND_SRC_ALPHA; + blend.DestBlend = D3D11_BLEND_ONE; + blend.BlendOp = D3D11_BLEND_OP_ADD; + blend.SrcBlendAlpha = D3D11_BLEND_SRC_ALPHA; + blend.DestBlendAlpha = D3D11_BLEND_ONE; + blend.BlendOpAlpha = D3D11_BLEND_OP_ADD; + blend.RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + blendDesc.RenderTarget[0] = blend; + hr = m_device->CreateBlendState(&blendDesc, &m_blendState); + if (FAILED(hr)) + qFatal("Failed to create blend state: 0x%x", hr); +} + +#include "d3d11squircle.moc" diff --git a/examples/quick/scenegraph/d3d11underqml/d3d11squircle.h b/examples/quick/scenegraph/d3d11underqml/d3d11squircle.h new file mode 100644 index 0000000000..be9aadc43b --- /dev/null +++ b/examples/quick/scenegraph/d3d11underqml/d3d11squircle.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** 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 D3D11SQUIRCLE_H +#define D3D11SQUIRCLE_H + +#include <QtQuick/QQuickItem> + +class SquircleRenderer; + +class D3D11Squircle : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(qreal t READ t WRITE setT NOTIFY tChanged) + +public: + D3D11Squircle(); + + qreal t() const { return m_t; } + void setT(qreal t); + +signals: + void tChanged(); + +public slots: + void sync(); + void cleanup(); + +private slots: + void handleWindowChanged(QQuickWindow *win); + +private: + void releaseResources() override; + + qreal m_t; + SquircleRenderer *m_renderer = nullptr; +}; + +#endif diff --git a/examples/quick/scenegraph/d3d11underqml/d3d11underqml.pro b/examples/quick/scenegraph/d3d11underqml/d3d11underqml.pro new file mode 100644 index 0000000000..7658a9a813 --- /dev/null +++ b/examples/quick/scenegraph/d3d11underqml/d3d11underqml.pro @@ -0,0 +1,12 @@ +!win32: error("This example requires Windows") + +QT += qml quick + +HEADERS += d3d11squircle.h +SOURCES += d3d11squircle.cpp main.cpp +RESOURCES += d3d11underqml.qrc + +LIBS += -ld3d11 -ld3dcompiler + +target.path = $$[QT_INSTALL_EXAMPLES]/quick/scenegraph/d3d11underqml +INSTALLS += target diff --git a/examples/quick/scenegraph/d3d11underqml/d3d11underqml.qrc b/examples/quick/scenegraph/d3d11underqml/d3d11underqml.qrc new file mode 100644 index 0000000000..fa16b88279 --- /dev/null +++ b/examples/quick/scenegraph/d3d11underqml/d3d11underqml.qrc @@ -0,0 +1,7 @@ +<RCC> + <qresource prefix="/scenegraph/d3d11underqml"> + <file>main.qml</file> + <file>squircle.vert</file> + <file>squircle.frag</file> + </qresource> +</RCC> diff --git a/examples/quick/scenegraph/d3d11underqml/doc/images/d3d11underqml-example.jpg b/examples/quick/scenegraph/d3d11underqml/doc/images/d3d11underqml-example.jpg Binary files differnew file mode 100644 index 0000000000..9f1e53ad61 --- /dev/null +++ b/examples/quick/scenegraph/d3d11underqml/doc/images/d3d11underqml-example.jpg 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..1f0c15e1c6 --- /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}, \l{Scene Graph - Metal Under QML}{Metal Under QML}, + and \l{Scene Graph - Vulkan Under QML}{Vulkan Under QML} examples, they all + render the same custom content, just via different native APIs. + + */ diff --git a/examples/quick/scenegraph/d3d11underqml/main.cpp b/examples/quick/scenegraph/d3d11underqml/main.cpp new file mode 100644 index 0000000000..d26de1144a --- /dev/null +++ b/examples/quick/scenegraph/d3d11underqml/main.cpp @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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 "d3d11squircle.h" + +int main(int argc, char **argv) +{ + QGuiApplication app(argc, argv); + + qmlRegisterType<D3D11Squircle>("D3D11UnderQML", 1, 0, "D3D11Squircle"); + + QQuickWindow::setSceneGraphBackend(QSGRendererInterface::Direct3D11Rhi); + + QQuickView view; + view.setResizeMode(QQuickView::SizeRootObjectToView); + view.setSource(QUrl("qrc:///scenegraph/d3d11underqml/main.qml")); + view.show(); + + return app.exec(); +} diff --git a/examples/quick/scenegraph/d3d11underqml/main.qml b/examples/quick/scenegraph/d3d11underqml/main.qml new file mode 100644 index 0000000000..da128bead6 --- /dev/null +++ b/examples/quick/scenegraph/d3d11underqml/main.qml @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +//! [1] +import QtQuick 2.0 +import D3D11UnderQML 1.0 + +Item { + + width: 320 + height: 480 + + D3D11Squircle { + 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 + } + } +//! [1] //! [2] + Rectangle { + color: Qt.rgba(1, 1, 1, 0.7) + radius: 10 + border.width: 1 + border.color: "white" + anchors.fill: label + anchors.margins: -10 + } + + Text { + id: label + color: "black" + wrapMode: Text.WordWrap + text: "The background here is a squircle rendered with raw Direct3D 11 using the beforeRendering() and beforeRenderPassRecording() signals in QQuickWindow. This text label and its border is rendered using QML" + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.margins: 20 + } +} +//! [2] diff --git a/examples/quick/scenegraph/d3d11underqml/squircle.frag b/examples/quick/scenegraph/d3d11underqml/squircle.frag new file mode 100644 index 0000000000..a907c84e58 --- /dev/null +++ b/examples/quick/scenegraph/d3d11underqml/squircle.frag @@ -0,0 +1,35 @@ +cbuffer buf : register(b0) +{ + float ubuf_t : packoffset(c0); +}; + + +static float2 coords; +static float4 fragColor; + +struct SPIRV_Cross_Input +{ + float2 coords : TEXCOORD0; +}; + +struct SPIRV_Cross_Output +{ + float4 fragColor : SV_Target0; +}; + +void frag_main() +{ + float i = 1.0f - (pow(abs(coords.x), 4.0f) + pow(abs(coords.y), 4.0f)); + i = smoothstep(ubuf_t - 0.800000011920928955078125f, ubuf_t + 0.800000011920928955078125f, i); + i = floor(i * 20.0f) / 20.0f; + fragColor = float4((coords * 0.5f) + 0.5f.xx, i, i); +} + +SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) +{ + coords = stage_input.coords; + frag_main(); + SPIRV_Cross_Output stage_output; + stage_output.fragColor = fragColor; + return stage_output; +} diff --git a/examples/quick/scenegraph/d3d11underqml/squircle.vert b/examples/quick/scenegraph/d3d11underqml/squircle.vert new file mode 100644 index 0000000000..077a1ad096 --- /dev/null +++ b/examples/quick/scenegraph/d3d11underqml/squircle.vert @@ -0,0 +1,30 @@ +static float4 gl_Position; +static float4 vertices; +static float2 coords; + +struct SPIRV_Cross_Input +{ + float4 vertices : TEXCOORD0; +}; + +struct SPIRV_Cross_Output +{ + float2 coords : TEXCOORD0; + float4 gl_Position : SV_Position; +}; + +void vert_main() +{ + gl_Position = vertices; + coords = vertices.xy; +} + +SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) +{ + vertices = stage_input.vertices; + vert_main(); + SPIRV_Cross_Output stage_output; + stage_output.gl_Position = gl_Position; + stage_output.coords = coords; + return stage_output; +} diff --git a/examples/quick/scenegraph/textureinsgnode/doc/images/textureinsgnode-example.jpg b/examples/quick/scenegraph/fboitem/doc/images/fboitem-example.jpg Binary files differindex 306b8bab20..306b8bab20 100644 --- a/examples/quick/scenegraph/textureinsgnode/doc/images/textureinsgnode-example.jpg +++ b/examples/quick/scenegraph/fboitem/doc/images/fboitem-example.jpg diff --git a/examples/quick/scenegraph/textureinsgnode/doc/src/textureinsgnode.qdoc b/examples/quick/scenegraph/fboitem/doc/src/fboitem.qdoc index c1c830338b..b5add02991 100644 --- a/examples/quick/scenegraph/textureinsgnode/doc/src/textureinsgnode.qdoc +++ b/examples/quick/scenegraph/fboitem/doc/src/fboitem.qdoc @@ -26,11 +26,11 @@ ****************************************************************************/ /*! - \example scenegraph/textureinsgnode + \example scenegraph/fboitem \title Scene Graph - Rendering FBOs \ingroup qtquickexamples \brief Shows how to use FramebufferObjects with Qt Quick. - \image textureinsgnode-example.jpg + \image fboitem-example.jpg */ diff --git a/examples/quick/scenegraph/textureinsgnode/fboinsgrenderer.cpp b/examples/quick/scenegraph/fboitem/fboinsgrenderer.cpp index 8ba5bddb2a..8ba5bddb2a 100644 --- a/examples/quick/scenegraph/textureinsgnode/fboinsgrenderer.cpp +++ b/examples/quick/scenegraph/fboitem/fboinsgrenderer.cpp diff --git a/examples/quick/scenegraph/textureinsgnode/fboinsgrenderer.h b/examples/quick/scenegraph/fboitem/fboinsgrenderer.h index e1a9ce22c8..e1a9ce22c8 100644 --- a/examples/quick/scenegraph/textureinsgnode/fboinsgrenderer.h +++ b/examples/quick/scenegraph/fboitem/fboinsgrenderer.h diff --git a/examples/quick/scenegraph/textureinsgnode/textureinsgnode.pro b/examples/quick/scenegraph/fboitem/fboitem.pro index 238e20a553..e40e5f4cf8 100644 --- a/examples/quick/scenegraph/textureinsgnode/textureinsgnode.pro +++ b/examples/quick/scenegraph/fboitem/fboitem.pro @@ -7,9 +7,9 @@ INCLUDEPATH += ../shared HEADERS += ../shared/logorenderer.h SOURCES += ../shared/logorenderer.cpp -RESOURCES += textureinsgnode.qrc +RESOURCES += fboitem.qrc -target.path = $$[QT_INSTALL_EXAMPLES]/quick/scenegraph/textureinsgnode +target.path = $$[QT_INSTALL_EXAMPLES]/quick/scenegraph/fboitem INSTALLS += target OTHER_FILES += \ diff --git a/examples/quick/scenegraph/textureinsgnode/textureinsgnode.qrc b/examples/quick/scenegraph/fboitem/fboitem.qrc index 9ecf0ada1c..9d9db70654 100644 --- a/examples/quick/scenegraph/textureinsgnode/textureinsgnode.qrc +++ b/examples/quick/scenegraph/fboitem/fboitem.qrc @@ -1,5 +1,5 @@ <RCC> - <qresource prefix="/scenegraph/textureinsgnode"> + <qresource prefix="/scenegraph/fboitem"> <file>main.qml</file> </qresource> </RCC> diff --git a/examples/quick/scenegraph/textureinsgnode/main.cpp b/examples/quick/scenegraph/fboitem/main.cpp index 8eececc0aa..429224ba95 100644 --- a/examples/quick/scenegraph/textureinsgnode/main.cpp +++ b/examples/quick/scenegraph/fboitem/main.cpp @@ -62,7 +62,7 @@ int main(int argc, char **argv) QQuickView view; view.setResizeMode(QQuickView::SizeRootObjectToView); - view.setSource(QUrl("qrc:///scenegraph/textureinsgnode/main.qml")); + view.setSource(QUrl("qrc:///scenegraph/fboitem/main.qml")); view.show(); return app.exec(); diff --git a/examples/quick/scenegraph/textureinsgnode/main.qml b/examples/quick/scenegraph/fboitem/main.qml index 92fa99e847..92fa99e847 100644 --- a/examples/quick/scenegraph/textureinsgnode/main.qml +++ b/examples/quick/scenegraph/fboitem/main.qml 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..66a39083f7 --- /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..5b11606946 --- /dev/null +++ b/examples/quick/scenegraph/metaltextureimport/metaltextureimport.pro @@ -0,0 +1,12 @@ +!macos: error("This example requires macOS") + +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/metalunderqml/doc/images/metalunderqml-example.jpg b/examples/quick/scenegraph/metalunderqml/doc/images/metalunderqml-example.jpg Binary files differnew file mode 100644 index 0000000000..98085773de --- /dev/null +++ b/examples/quick/scenegraph/metalunderqml/doc/images/metalunderqml-example.jpg 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..fb28270315 --- /dev/null +++ b/examples/quick/scenegraph/metalunderqml/doc/src/metalunderqml.qdoc @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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}, \l{Scene Graph - Direct3D 11 Under QML}{Direct3D 11 + Under QML}, and \l{Scene Graph - Vulkan Under QML}{Vulkan Under QML} + examples, they all render the same custom content, just via different + native APIs. + + */ diff --git a/examples/quick/scenegraph/metalunderqml/main.cpp b/examples/quick/scenegraph/metalunderqml/main.cpp new file mode 100644 index 0000000000..5ad337abb1 --- /dev/null +++ b/examples/quick/scenegraph/metalunderqml/main.cpp @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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 "metalsquircle.h" + +int main(int argc, char **argv) +{ + QGuiApplication app(argc, argv); + + qmlRegisterType<MetalSquircle>("MetalUnderQML", 1, 0, "MetalSquircle"); + + QQuickWindow::setSceneGraphBackend(QSGRendererInterface::MetalRhi); + + QQuickView view; + view.setResizeMode(QQuickView::SizeRootObjectToView); + view.setSource(QUrl("qrc:///scenegraph/metalunderqml/main.qml")); + view.show(); + + return app.exec(); +} diff --git a/examples/quick/scenegraph/metalunderqml/main.qml b/examples/quick/scenegraph/metalunderqml/main.qml new file mode 100644 index 0000000000..d41a0b8e81 --- /dev/null +++ b/examples/quick/scenegraph/metalunderqml/main.qml @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +//! [1] +import QtQuick 2.0 +import MetalUnderQML 1.0 + +Item { + + width: 320 + height: 480 + + MetalSquircle { + 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 + } + } +//! [1] //! [2] + Rectangle { + color: Qt.rgba(1, 1, 1, 0.7) + radius: 10 + border.width: 1 + border.color: "white" + anchors.fill: label + anchors.margins: -10 + } + + Text { + id: label + color: "black" + wrapMode: Text.WordWrap + text: "The background here is a squircle rendered with raw Metal using the beforeRendering() and beforeRenderPassRecording() signals in QQuickWindow. This text label and its border is rendered using QML" + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.margins: 20 + } +} +//! [2] diff --git a/examples/quick/scenegraph/metalunderqml/metalsquircle.h b/examples/quick/scenegraph/metalunderqml/metalsquircle.h new file mode 100644 index 0000000000..43c4afad21 --- /dev/null +++ b/examples/quick/scenegraph/metalunderqml/metalsquircle.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** 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 METALSQUIRCLE_H +#define METALSQUIRCLE_H + +#include <QtQuick/QQuickItem> + +class SquircleRenderer; + +class MetalSquircle : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(qreal t READ t WRITE setT NOTIFY tChanged) + +public: + MetalSquircle(); + + qreal t() const { return m_t; } + void setT(qreal t); + +signals: + void tChanged(); + +public slots: + void sync(); + void cleanup(); + +private slots: + void handleWindowChanged(QQuickWindow *win); + +private: + void releaseResources() override; + + qreal m_t; + SquircleRenderer *m_renderer = nullptr; +}; + +#endif diff --git a/examples/quick/scenegraph/metalunderqml/metalsquircle.mm b/examples/quick/scenegraph/metalunderqml/metalsquircle.mm new file mode 100644 index 0000000000..5ca6daa01a --- /dev/null +++ b/examples/quick/scenegraph/metalunderqml/metalsquircle.mm @@ -0,0 +1,369 @@ +/**************************************************************************** +** +** 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 "metalsquircle.h" +#include <QtCore/QRunnable> +#include <QtQuick/QQuickWindow> + +#include <Metal/Metal.h> + +class SquircleRenderer : public QObject +{ + Q_OBJECT +public: + SquircleRenderer(); + ~SquircleRenderer(); + + void setT(qreal t) { m_t = t; } + void setViewportSize(const QSize &size) { m_viewportSize = size; } + void setWindow(QQuickWindow *window) { m_window = window; } + +public slots: + void frameStart(); + void mainPassRecordingStart(); + +private: + enum Stage { + VertexStage, + FragmentStage + }; + void prepareShader(Stage stage); + using FuncAndLib = QPair<id<MTLFunction>, id<MTLLibrary> >; + FuncAndLib compileShaderFromSource(const QByteArray &src, const QByteArray &entryPoint); + void init(int framesInFlight); + + QSize m_viewportSize; + qreal m_t; + QQuickWindow *m_window; + + QByteArray m_vert; + QByteArray m_vertEntryPoint; + QByteArray m_frag; + QByteArray m_fragEntryPoint; + + bool m_initialized = false; + id<MTLDevice> m_device; + id<MTLBuffer> m_vbuf; + id<MTLBuffer> m_ubuf[3]; + FuncAndLib m_vs; + FuncAndLib m_fs; + id<MTLRenderPipelineState> m_pipeline; +}; + +MetalSquircle::MetalSquircle() + : m_t(0) + , m_renderer(nullptr) +{ + connect(this, &QQuickItem::windowChanged, this, &MetalSquircle::handleWindowChanged); +} + +void MetalSquircle::setT(qreal t) +{ + if (t == m_t) + return; + m_t = t; + emit tChanged(); + if (window()) + window()->update(); +} + +void MetalSquircle::handleWindowChanged(QQuickWindow *win) +{ + if (win) { + connect(win, &QQuickWindow::beforeSynchronizing, this, &MetalSquircle::sync, Qt::DirectConnection); + connect(win, &QQuickWindow::sceneGraphInvalidated, this, &MetalSquircle::cleanup, Qt::DirectConnection); + + // Ensure we start with cleared to black. The squircle's blend mode relies on this. + win->setColor(Qt::black); + } +} + +SquircleRenderer::SquircleRenderer() + : m_t(0) +{ + m_vbuf = nil; + + for (int i = 0; i < 3; ++i) + m_ubuf[i] = nil; + + m_vs.first = nil; + m_vs.second = nil; + + m_fs.first = nil; + m_fs.second = nil; +} + +// The safe way to release custom graphics resources is to both connect to +// sceneGraphInvalidated() and implement releaseResources(). To support +// threaded render loops the latter performs the SquircleRenderer destruction +// via scheduleRenderJob(). Note that the MetalSquircle may be gone by the time +// the QRunnable is invoked. + +void MetalSquircle::cleanup() +{ + delete m_renderer; + m_renderer = nullptr; +} + +class CleanupJob : public QRunnable +{ +public: + CleanupJob(SquircleRenderer *renderer) : m_renderer(renderer) { } + void run() override { delete m_renderer; } +private: + SquircleRenderer *m_renderer; +}; + +void MetalSquircle::releaseResources() +{ + window()->scheduleRenderJob(new CleanupJob(m_renderer), QQuickWindow::BeforeSynchronizingStage); + m_renderer = nullptr; +} + +SquircleRenderer::~SquircleRenderer() +{ + qDebug("cleanup"); + + [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]; +} + +void MetalSquircle::sync() +{ + if (!m_renderer) { + m_renderer = new SquircleRenderer; + // Initializing resources is done before starting to encode render + // commands, regardless of wanting an underlay or overlay. + connect(window(), &QQuickWindow::beforeRendering, m_renderer, &SquircleRenderer::frameStart, Qt::DirectConnection); + // Here we want an underlay and therefore connect to + // beforeRenderPassRecording. Changing to afterRenderPassRecording + // would render the squircle on top (overlay). + connect(window(), &QQuickWindow::beforeRenderPassRecording, m_renderer, &SquircleRenderer::mainPassRecordingStart, Qt::DirectConnection); + } + m_renderer->setViewportSize(window()->size() * window()->devicePixelRatio()); + m_renderer->setT(m_t); + m_renderer->setWindow(window()); +} + +void SquircleRenderer::frameStart() +{ + QSGRendererInterface *rif = m_window->rendererInterface(); + + // We are not prepared for anything other than running with the RHI and its Metal backend. + Q_ASSERT(rif->graphicsApi() == QSGRendererInterface::MetalRhi); + + m_device = (id<MTLDevice>) rif->getResource(m_window, QSGRendererInterface::DeviceResource); + Q_ASSERT(m_device); + + if (m_vert.isEmpty()) + prepareShader(VertexStage); + if (m_frag.isEmpty()) + prepareShader(FragmentStage); + + if (!m_initialized) + init(m_window->graphicsStateInfo().framesInFlight); +} + +static const float vertices[] = { + -1, -1, + 1, -1, + -1, 1, + 1, 1 +}; + +const int UBUF_SIZE = 4; + +void SquircleRenderer::mainPassRecordingStart() +{ + // This example demonstrates the simple case: prepending some commands to + // the scenegraph's main renderpass. It does not create its own passes, + // rendertargets, etc. so no synchronization is needed. + + const QQuickWindow::GraphicsStateInfo &stateInfo(m_window->graphicsStateInfo()); + + QSGRendererInterface *rif = m_window->rendererInterface(); + id<MTLRenderCommandEncoder> encoder = (id<MTLRenderCommandEncoder>) rif->getResource( + m_window, QSGRendererInterface::CommandEncoderResource); + Q_ASSERT(encoder); + + m_window->beginExternalCommands(); + + void *p = [m_ubuf[stateInfo.currentFrameSlot] contents]; + float t = m_t; + memcpy(p, &t, 4); + + MTLViewport vp; + vp.originX = 0; + vp.originY = 0; + vp.width = m_viewportSize.width(); + vp.height = m_viewportSize.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]; + + m_window->endExternalCommands(); +} + +void SquircleRenderer::prepareShader(Stage stage) +{ + QString filename; + if (stage == VertexStage) { + filename = QLatin1String(":/scenegraph/metalunderqml/squircle.vert"); + } else { + Q_ASSERT(stage == FragmentStage); + filename = QLatin1String(":/scenegraph/metalunderqml/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"); + } +} + +SquircleRenderer::FuncAndLib SquircleRenderer::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; +} + +void SquircleRenderer::init(int framesInFlight) +{ + qDebug("init"); + + Q_ASSERT(framesInFlight <= 3); + m_initialized = true; + + 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; + + m_vs = compileShaderFromSource(m_vert, m_vertEntryPoint); + rpDesc.vertexFunction = m_vs.first; + m_fs = compileShaderFromSource(m_frag, m_fragEntryPoint); + rpDesc.fragmentFunction = m_fs.first; + + rpDesc.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm; + 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; + + if (m_device.depth24Stencil8PixelFormatSupported) { + rpDesc.depthAttachmentPixelFormat = MTLPixelFormatDepth24Unorm_Stencil8; + rpDesc.stencilAttachmentPixelFormat = MTLPixelFormatDepth24Unorm_Stencil8; + } else { + rpDesc.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8; + rpDesc.stencilAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8; + } + + 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]; +} + +#include "metalsquircle.moc" diff --git a/examples/quick/scenegraph/metalunderqml/metalunderqml.pro b/examples/quick/scenegraph/metalunderqml/metalunderqml.pro new file mode 100644 index 0000000000..9fd131fe1b --- /dev/null +++ b/examples/quick/scenegraph/metalunderqml/metalunderqml.pro @@ -0,0 +1,12 @@ +!macos: error("This example requires macOS") + +QT += qml quick + +HEADERS += metalsquircle.h +SOURCES += metalsquircle.mm main.cpp +RESOURCES += metalunderqml.qrc + +LIBS += -framework Metal -framework AppKit + +target.path = $$[QT_INSTALL_EXAMPLES]/quick/scenegraph/metalunderqml +INSTALLS += target diff --git a/examples/quick/scenegraph/metalunderqml/metalunderqml.qrc b/examples/quick/scenegraph/metalunderqml/metalunderqml.qrc new file mode 100644 index 0000000000..7172f1fcb7 --- /dev/null +++ b/examples/quick/scenegraph/metalunderqml/metalunderqml.qrc @@ -0,0 +1,7 @@ +<RCC> + <qresource prefix="/scenegraph/metalunderqml"> + <file>main.qml</file> + <file>squircle.vert</file> + <file>squircle.frag</file> + </qresource> +</RCC> diff --git a/examples/quick/scenegraph/metalunderqml/squircle.frag b/examples/quick/scenegraph/metalunderqml/squircle.frag new file mode 100644 index 0000000000..15f34624fe --- /dev/null +++ b/examples/quick/scenegraph/metalunderqml/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/metalunderqml/squircle.vert b/examples/quick/scenegraph/metalunderqml/squircle.vert new file mode 100644 index 0000000000..a88c59f541 --- /dev/null +++ b/examples/quick/scenegraph/metalunderqml/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/openglunderqml/doc/src/openglunderqml.qdoc b/examples/quick/scenegraph/openglunderqml/doc/src/openglunderqml.qdoc index 3d4f4443e9..9676815c44 100644 --- a/examples/quick/scenegraph/openglunderqml/doc/src/openglunderqml.qdoc +++ b/examples/quick/scenegraph/openglunderqml/doc/src/openglunderqml.qdoc @@ -50,6 +50,12 @@ 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}, \l{Scene Graph - Metal Under QML}{Metal + Under QML}, and \l{Scene Graph - Vulkan Under QML}{Vulkan 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 @@ -94,17 +100,18 @@ \snippet scenegraph/openglunderqml/squircle.cpp 3 - The default behavior of the scene graph is to clear the - framebuffer before rendering. Since we render before the scene - graph, we need to turn this clearing off. This means that we need - to clear ourselves in the \c paint() function. + The default behavior of the scene graph is to clear the framebuffer before + rendering. This is fine since we will insert our own rendering code after + this clear is enqueued. Make sure however that we clear to the desired + color (black). \snippet scenegraph/openglunderqml/squircle.cpp 9 - We use the \c sync() function to initialize the renderer and to - copy the state in our item into the renderer. When the renderer is - created, we also connect the \l QQuickWindow::beforeRendering() to - the renderer's \c paint() slot. + We use the \c sync() function to initialize the renderer and to copy the + state in our item into the renderer. When the renderer is created, we also + connect the \l QQuickWindow::beforeRendering() and \l + QQuickWindow::beforeRenderPassRecording() to the renderer's \c init() and + \c paint() slots. \note The \l QQuickWindow::beforeSynchronizing() signal is emitted on the rendering thread while the GUI thread is blocked, so it is @@ -112,8 +119,11 @@ \snippet scenegraph/openglunderqml/squircle.cpp 6 - In the \c cleanup() function we delete the renderer which in turn - cleans up its own resources. + In the \c cleanup() function we delete the renderer which in turn cleans up + its own resources. This is complemented by reimplementing \l + QQuickWindow::releaseResources() since just connecting to the + sceneGraphInvalidated() signal is not sufficient on its own to handle all + cases. \snippet scenegraph/openglunderqml/squircle.cpp 8 @@ -124,22 +134,13 @@ \snippet scenegraph/openglunderqml/squircle.cpp 4 - In the SquircleRenderer's \c paint() function we start by - initializing the shader program. By initializing the shader - program here, we make sure that the OpenGL context is bound and - that we are on the correct thread. + In the SquircleRenderer's \c init() function we start by initializing the + shader program if not yet done. The OpenGL context is current on the thread + when the slot is invoked. \snippet scenegraph/openglunderqml/squircle.cpp 5 - We use the shader program to draw the squircle. At the end of the - \c paint function we release the program and disable the - attributes we used so that the OpenGL context is in a "clean" - state for the scene graph to pick it up. - - \note If tracking the changes in the OpenGL context's state is not - feasible, one can use the function \l - QQuickWindow::resetOpenGLState() which will reset all state that - the scene graph relies on. + We use the shader program to draw the squircle in \c paint(). \snippet scenegraph/openglunderqml/main.cpp 1 diff --git a/examples/quick/scenegraph/openglunderqml/squircle.cpp b/examples/quick/scenegraph/openglunderqml/squircle.cpp index d6f6b327f2..828857fe24 100644 --- a/examples/quick/scenegraph/openglunderqml/squircle.cpp +++ b/examples/quick/scenegraph/openglunderqml/squircle.cpp @@ -53,6 +53,7 @@ #include <QtQuick/qquickwindow.h> #include <QtGui/QOpenGLShaderProgram> #include <QtGui/QOpenGLContext> +#include <QtCore/QRunnable> //! [7] Squircle::Squircle() @@ -82,10 +83,9 @@ void Squircle::handleWindowChanged(QQuickWindow *win) connect(win, &QQuickWindow::beforeSynchronizing, this, &Squircle::sync, Qt::DirectConnection); connect(win, &QQuickWindow::sceneGraphInvalidated, this, &Squircle::cleanup, Qt::DirectConnection); //! [1] - // If we allow QML to do the clearing, they would clear what we paint - // and nothing would show. //! [3] - win->setClearBeforeRendering(false); + // Ensure we start with cleared to black. The squircle's blend mode relies on this. + win->setColor(Qt::black); } } //! [3] @@ -93,10 +93,23 @@ void Squircle::handleWindowChanged(QQuickWindow *win) //! [6] void Squircle::cleanup() { - if (m_renderer) { - delete m_renderer; - m_renderer = nullptr; - } + delete m_renderer; + m_renderer = nullptr; +} + +class CleanupJob : public QRunnable +{ +public: + CleanupJob(SquircleRenderer *renderer) : m_renderer(renderer) { } + void run() override { delete m_renderer; } +private: + SquircleRenderer *m_renderer; +}; + +void Squircle::releaseResources() +{ + window()->scheduleRenderJob(new CleanupJob(m_renderer), QQuickWindow::BeforeSynchronizingStage); + m_renderer = nullptr; } SquircleRenderer::~SquircleRenderer() @@ -110,7 +123,8 @@ void Squircle::sync() { if (!m_renderer) { m_renderer = new SquircleRenderer(); - connect(window(), &QQuickWindow::beforeRendering, m_renderer, &SquircleRenderer::paint, Qt::DirectConnection); + connect(window(), &QQuickWindow::beforeRendering, m_renderer, &SquircleRenderer::init, Qt::DirectConnection); + connect(window(), &QQuickWindow::beforeRenderPassRecording, m_renderer, &SquircleRenderer::paint, Qt::DirectConnection); } m_renderer->setViewportSize(window()->size() * window()->devicePixelRatio()); m_renderer->setT(m_t); @@ -119,9 +133,12 @@ void Squircle::sync() //! [9] //! [4] -void SquircleRenderer::paint() +void SquircleRenderer::init() { if (!m_program) { + QSGRendererInterface *rif = m_window->rendererInterface(); + Q_ASSERT(rif->graphicsApi() == QSGRendererInterface::OpenGL || rif->graphicsApi() == QSGRendererInterface::OpenGLRhi); + initializeOpenGLFunctions(); m_program = new QOpenGLShaderProgram(); @@ -146,7 +163,15 @@ void SquircleRenderer::paint() m_program->link(); } +} + //! [4] //! [5] +void SquircleRenderer::paint() +{ + // Play nice with the RHI. Not strictly needed when the scenegraph uses + // OpenGL directly. + m_window->beginExternalCommands(); + m_program->bind(); m_program->enableAttributeArray(0); @@ -157,6 +182,11 @@ void SquircleRenderer::paint() -1, 1, 1, 1 }; + + // This example relies on (deprecated) client-side pointers for the vertex + // input. Therefore, we have to make sure no vertex buffer is bound. + glBindBuffer(GL_ARRAY_BUFFER, 0); + m_program->setAttributeArray(0, GL_FLOAT, values, 2); m_program->setUniformValue("t", (float) m_t); @@ -164,9 +194,6 @@ void SquircleRenderer::paint() glDisable(GL_DEPTH_TEST); - glClearColor(0, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); @@ -178,5 +205,7 @@ void SquircleRenderer::paint() // Not strictly needed for this example, but generally useful for when // mixing with raw OpenGL. m_window->resetOpenGLState(); + + m_window->endExternalCommands(); } //! [5] diff --git a/examples/quick/scenegraph/openglunderqml/squircle.h b/examples/quick/scenegraph/openglunderqml/squircle.h index 652e679f81..1b9995bc1e 100644 --- a/examples/quick/scenegraph/openglunderqml/squircle.h +++ b/examples/quick/scenegraph/openglunderqml/squircle.h @@ -70,6 +70,7 @@ public: void setWindow(QQuickWindow *window) { m_window = window; } public slots: + void init(); void paint(); private: @@ -103,6 +104,8 @@ private slots: void handleWindowChanged(QQuickWindow *win); private: + void releaseResources() override; + qreal m_t; SquircleRenderer *m_renderer; }; diff --git a/examples/quick/scenegraph/rendernode/customrenderitem.cpp b/examples/quick/scenegraph/rendernode/customrenderitem.cpp index 8f248e2ecb..e55cf0a2f4 100644 --- a/examples/quick/scenegraph/rendernode/customrenderitem.cpp +++ b/examples/quick/scenegraph/rendernode/customrenderitem.cpp @@ -53,42 +53,78 @@ #include <QSGRendererInterface> #include "openglrenderer.h" +#include "metalrenderer.h" #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<QSGRenderNode *>(node); - if (!n) { - QSGRendererInterface *ri = window()->rendererInterface(); - if (!ri) - return nullptr; - switch (ri->graphicsApi()) { - case QSGRendererInterface::OpenGL: + + QSGRendererInterface *ri = window()->rendererInterface(); + if (!ri) + return nullptr; + + switch (ri->graphicsApi()) { + case QSGRendererInterface::OpenGL: + Q_FALLTHROUGH(); + case QSGRendererInterface::OpenGLRhi: #if QT_CONFIG(opengl) - n = new OpenGLRenderNode(this); - break; + if (!n) + n = new OpenGLRenderNode; + static_cast<OpenGLRenderNode *>(n)->sync(this); +#endif + break; + + case QSGRendererInterface::MetalRhi: +// Restore when QTBUG-78580 is done and the .pro is updated accordingly +//#ifdef Q_OS_DARWIN +#ifdef Q_OS_MACOS + if (!n) { + MetalRenderNode *metalNode = new MetalRenderNode; + n = metalNode; + metalNode->resourceBuilder()->setWindow(window()); + QObject::connect(window(), &QQuickWindow::beforeRendering, + metalNode->resourceBuilder(), &MetalRenderNodeResourceBuilder::build); + } + static_cast<MetalRenderNode *>(n)->sync(this); #endif - case QSGRendererInterface::Direct3D12: + break; + + case QSGRendererInterface::Direct3D12: // ### Qt 6: remove #if QT_CONFIG(d3d12) - n = new D3D12RenderNode(this); - break; + if (!n) + n = new D3D12RenderNode; + static_cast<D3D12RenderNode *>(n)->sync(this); #endif - case QSGRendererInterface::Software: - n = new SoftwareRenderNode(this); - break; + break; - default: - return nullptr; - } + case QSGRendererInterface::Software: + if (!n) + n = new SoftwareRenderNode; + static_cast<SoftwareRenderNode *>(n)->sync(this); + break; + + default: + break; } + if (!n) + qWarning("QSGRendererInterface reports unknown graphics API %d", ri->graphicsApi()); + 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/d3d12renderer.cpp b/examples/quick/scenegraph/rendernode/d3d12renderer.cpp index df0e29eb67..e85811c089 100644 --- a/examples/quick/scenegraph/rendernode/d3d12renderer.cpp +++ b/examples/quick/scenegraph/rendernode/d3d12renderer.cpp @@ -54,12 +54,9 @@ #include <QSGRendererInterface> #include <QFile> -#if QT_CONFIG(d3d12) +// ### Qt 6: remove -D3D12RenderNode::D3D12RenderNode(QQuickItem *item) - : m_item(item) -{ -} +#if QT_CONFIG(d3d12) D3D12RenderNode::~D3D12RenderNode() { @@ -85,8 +82,8 @@ void D3D12RenderNode::releaseResources() void D3D12RenderNode::init() { - QSGRendererInterface *rif = m_item->window()->rendererInterface(); - m_device = static_cast<ID3D12Device *>(rif->getResource(m_item->window(), QSGRendererInterface::DeviceResource)); + QSGRendererInterface *rif = m_window->rendererInterface(); + m_device = static_cast<ID3D12Device *>(rif->getResource(m_window, QSGRendererInterface::DeviceResource)); Q_ASSERT(m_device); D3D12_ROOT_PARAMETER rootParameter; @@ -166,8 +163,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; @@ -176,7 +173,7 @@ void D3D12RenderNode::init() psoDesc.DSVFormat = DXGI_FORMAT_D24_UNORM_S8_UINT; // not in use due to !DepthEnable, but this would be the correct format otherwise // We are rendering on the default render target so if the QuickWindow/View // has requested samples > 0 then we have to follow suit. - const uint samples = qMax(1, m_item->window()->format().samples()); + const uint samples = qMax(1, m_window->format().samples()); psoDesc.SampleDesc.Count = samples; if (samples > 1) { D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msaaInfo = {}; @@ -255,9 +252,9 @@ void D3D12RenderNode::render(const RenderState *state) if (!m_device) init(); - QSGRendererInterface *rif = m_item->window()->rendererInterface(); + QSGRendererInterface *rif = m_window->rendererInterface(); ID3D12GraphicsCommandList *commandList = static_cast<ID3D12GraphicsCommandList *>( - rif->getResource(m_item->window(), QSGRendererInterface::CommandListResource)); + rif->getResource(m_window, QSGRendererInterface::CommandListResource)); Q_ASSERT(commandList); const int msize = 16 * sizeof(float); @@ -266,9 +263,9 @@ void D3D12RenderNode::render(const RenderState *state) const float opacity = inheritedOpacity(); memcpy(cbPtr + 2 * msize, &opacity, sizeof(float)); - const QPointF p0(m_item->width() - 1, m_item->height() - 1); + const QPointF p0(m_width - 1, m_height - 1); const QPointF p1(0, 0); - const QPointF p2(0, m_item->height() - 1); + const QPointF p2(0, m_height - 1); float *vp = reinterpret_cast<float *>(vbPtr); *vp++ = p0.x(); @@ -299,7 +296,14 @@ QSGRenderNode::RenderingFlags D3D12RenderNode::flags() const QRectF D3D12RenderNode::rect() const { - return QRect(0, 0, m_item->width(), m_item->height()); + return QRect(0, 0, m_width, m_height); +} + +void D3D12RenderNode::sync(QQuickItem *item) +{ + m_window = item->window(); + m_width = item->width(); + m_height = item->height(); } #endif // d3d12 diff --git a/examples/quick/scenegraph/rendernode/d3d12renderer.h b/examples/quick/scenegraph/rendernode/d3d12renderer.h index ec4b5f85e8..7186b72c04 100644 --- a/examples/quick/scenegraph/rendernode/d3d12renderer.h +++ b/examples/quick/scenegraph/rendernode/d3d12renderer.h @@ -52,11 +52,10 @@ #define D3D12RENDERER_H #include <qsgrendernode.h> +#include <QQuickItem> #if QT_CONFIG(d3d12) -QT_FORWARD_DECLARE_CLASS(QQuickItem) - #include <d3d12.h> #include <wrl/client.h> @@ -65,7 +64,6 @@ using namespace Microsoft::WRL; class D3D12RenderNode : public QSGRenderNode { public: - D3D12RenderNode(QQuickItem *item); ~D3D12RenderNode(); void render(const RenderState *state) override; @@ -73,10 +71,15 @@ public: RenderingFlags flags() const override; QRectF rect() const override; + void sync(QQuickItem *item); + private: void init(); - QQuickItem *m_item; + QQuickWindow *m_window = nullptr; + int m_width = 0; + int m_height = 0; + ID3D12Device *m_device = nullptr; ComPtr<ID3D12PipelineState> pipelineState; ComPtr<ID3D12RootSignature> rootSignature; diff --git a/examples/quick/scenegraph/rendernode/doc/images/rendernode-example.jpg b/examples/quick/scenegraph/rendernode/doc/images/rendernode-example.jpg Binary files differnew file mode 100644 index 0000000000..cbb59b950d --- /dev/null +++ b/examples/quick/scenegraph/rendernode/doc/images/rendernode-example.jpg 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..ba6551fddf --- /dev/null +++ b/examples/quick/scenegraph/rendernode/doc/src/rendernode.qdoc @@ -0,0 +1,165 @@ +/**************************************************************************** +** +** 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 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 + 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<CustomRenderItem>("SceneGraphRendering", 2, 0, "CustomRenderItem"); +//! [1] QQuickView view; diff --git a/examples/quick/scenegraph/rendernode/main.qml b/examples/quick/scenegraph/rendernode/main.qml index d0ba4a4669..153a71e097 100644 --- a/examples/quick/scenegraph/rendernode/main.qml +++ b/examples/quick/scenegraph/rendernode/main.qml @@ -49,27 +49,65 @@ ****************************************************************************/ import QtQuick 2.8 +//! [2] import SceneGraphRendering 2.0 +//! [2] Item { Rectangle { + id: bg anchors.fill: parent gradient: Gradient { GradientStop { position: 0; color: "steelblue" } GradientStop { position: 1; color: "black" } } - CustomRenderItem { - id: renderer + //! [5] + 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; + } + } + } + // ![5] + + 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 + } + + //! [3] + 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 } + ] + } + //! [3] } SequentialAnimation { @@ -92,19 +130,39 @@ Item { loops: Animation.Infinite } + //! [4] 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; + case GraphicsInfo.MetalRhi: apiStr = "Metal (RHI)"; break; + // the example has no other QSGRenderNode subclasses + default: apiStr = "<UNSUPPORTED>"; 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)" + } + // ![4] + } + + 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/metalrenderer.h b/examples/quick/scenegraph/rendernode/metalrenderer.h new file mode 100644 index 0000000000..cf7fccb930 --- /dev/null +++ b/examples/quick/scenegraph/rendernode/metalrenderer.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** 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 <qsgrendernode.h> +#include <QQuickItem> + +//#ifdef Q_OS_DARWIN +#ifdef Q_OS_MACOS + +QT_BEGIN_NAMESPACE + +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(); + ~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; } + + void sync(QQuickItem *item); + +private: + MetalRenderNodeResourceBuilder m_resourceBuilder; + QQuickWindow *m_window = nullptr; + int m_width = 0; + int m_height = 0; + int m_outputHeight = 0; +}; + +#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..b83dc62c48 --- /dev/null +++ b/examples/quick/scenegraph/rendernode/metalrenderer.mm @@ -0,0 +1,333 @@ +/**************************************************************************** +** +** 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 <QQuickItem> +#include <QQuickWindow> + +#include <Metal/Metal.h> + +using FuncAndLib = QPair<id<MTLFunction>, id<MTLLibrary> >; + +const int MAX_FRAMES_IN_FLIGHT = 3; + +struct { + id<MTLDevice> dev = nil; + QByteArray vsSource; + FuncAndLib vs; + QByteArray fsSource; + FuncAndLib fs; + id<MTLBuffer> vbuf[MAX_FRAMES_IN_FLIGHT]; + id<MTLBuffer> ubuf[MAX_FRAMES_IN_FLIGHT]; + id<MTLDepthStencilState> stencilEnabledDsState = nil; + id<MTLRenderPipelineState> 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<MTLDevice>) 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() +{ + 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) +{ + Q_ASSERT(m_window); + const QQuickWindow::GraphicsStateInfo &stateInfo(m_window->graphicsStateInfo()); + id<MTLBuffer> vbuf = g.vbuf[stateInfo.currentFrameSlot]; + id<MTLBuffer> ubuf = g.ubuf[stateInfo.currentFrameSlot]; + + QPointF p0(m_width - 1, m_height - 1); + QPointF p1(0, 0); + QPointF p2(0, m_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 = m_window->rendererInterface(); + id<MTLRenderCommandEncoder> encoder = (id<MTLRenderCommandEncoder>) rif->getResource( + m_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 = m_outputHeight - (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_width, m_height); +} + +void MetalRenderNode::sync(QQuickItem *item) +{ + m_window = item->window(); + m_width = item->width(); + m_height = item->height(); + m_outputHeight = m_window->height() * m_window->effectiveDevicePixelRatio(); +} 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 <metal_stdlib> +#include <simd/simd.h> + +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 <metal_stdlib> +#include <simd/simd.h> + +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/openglrenderer.cpp b/examples/quick/scenegraph/rendernode/openglrenderer.cpp index 3c68830db6..a4e619bea9 100644 --- a/examples/quick/scenegraph/rendernode/openglrenderer.cpp +++ b/examples/quick/scenegraph/rendernode/openglrenderer.cpp @@ -57,11 +57,7 @@ #include <QOpenGLBuffer> #include <QOpenGLFunctions> -OpenGLRenderNode::OpenGLRenderNode(QQuickItem *item) - : m_item(item) -{ -} - +//! [1] OpenGLRenderNode::~OpenGLRenderNode() { releaseResources(); @@ -74,6 +70,7 @@ void OpenGLRenderNode::releaseResources() delete m_vbo; m_vbo = nullptr; } +//! [1] void OpenGLRenderNode::init() { @@ -121,44 +118,66 @@ 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(); - QPointF p0(m_item->width() - 1, m_item->height() - 1); +//! [5] + QPointF p0(m_width - 1, m_height - 1); QPointF p1(0, 0); - QPointF p2(0, m_item->height() - 1); + QPointF p2(0, m_height - 1); GLfloat vertices[6] = { GLfloat(p0.x()), GLfloat(p0.y()), 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); 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. + + //! [3] + 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); + //! [3] } +//! [4] QSGRenderNode::StateFlags OpenGLRenderNode::changedStates() const { - return BlendState; + return BlendState | ScissorState | StencilState; } QSGRenderNode::RenderingFlags OpenGLRenderNode::flags() const @@ -168,7 +187,14 @@ QSGRenderNode::RenderingFlags OpenGLRenderNode::flags() const QRectF OpenGLRenderNode::rect() const { - return QRect(0, 0, m_item->width(), m_item->height()); + return QRect(0, 0, m_width, m_height); +} +//! [4] + +void OpenGLRenderNode::sync(QQuickItem *item) +{ + m_width = item->width(); + m_height = item->height(); } #endif // opengl diff --git a/examples/quick/scenegraph/rendernode/openglrenderer.h b/examples/quick/scenegraph/rendernode/openglrenderer.h index ac640405c5..1e7977481a 100644 --- a/examples/quick/scenegraph/rendernode/openglrenderer.h +++ b/examples/quick/scenegraph/rendernode/openglrenderer.h @@ -52,21 +52,21 @@ #define OPENGLRENDERER_H #include <qsgrendernode.h> +#include <QQuickItem> #if QT_CONFIG(opengl) QT_BEGIN_NAMESPACE -class QQuickItem; class QOpenGLShaderProgram; class QOpenGLBuffer; QT_END_NAMESPACE +//! [1] class OpenGLRenderNode : public QSGRenderNode { public: - OpenGLRenderNode(QQuickItem *item); ~OpenGLRenderNode(); void render(const RenderState *state) override; @@ -74,11 +74,15 @@ public: StateFlags changedStates() const override; RenderingFlags flags() const override; QRectF rect() const override; +//! [1] + + void sync(QQuickItem *item); private: void init(); - QQuickItem *m_item; + int m_width = 0; + int m_height = 0; QOpenGLShaderProgram *m_program = nullptr; int m_matrixUniform; int m_opacityUniform; 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 @@ <file>main.qml</file> <file>shader_vert.cso</file> <file>shader_frag.cso</file> + <file>metalshader.vert</file> + <file>metalshader.frag</file> </qresource> </RCC> diff --git a/examples/quick/scenegraph/rendernode/softwarerenderer.cpp b/examples/quick/scenegraph/rendernode/softwarerenderer.cpp index 0a0ec4b485..bba364ac97 100644 --- a/examples/quick/scenegraph/rendernode/softwarerenderer.cpp +++ b/examples/quick/scenegraph/rendernode/softwarerenderer.cpp @@ -54,11 +54,6 @@ #include <QSGRendererInterface> #include <QPainter> -SoftwareRenderNode::SoftwareRenderNode(QQuickItem *item) - : m_item(item) -{ -} - SoftwareRenderNode::~SoftwareRenderNode() { releaseResources(); @@ -70,8 +65,10 @@ void SoftwareRenderNode::releaseResources() void SoftwareRenderNode::render(const RenderState *renderState) { - QSGRendererInterface *rif = m_item->window()->rendererInterface(); - QPainter *p = static_cast<QPainter *>(rif->getResource(m_item->window(), QSGRendererInterface::PainterResource)); + Q_ASSERT(m_window); + + QSGRendererInterface *rif = m_window->rendererInterface(); + QPainter *p = static_cast<QPainter *>(rif->getResource(m_window, QSGRendererInterface::PainterResource)); Q_ASSERT(p); const QRegion *clipRegion = renderState->clipRegion(); @@ -81,15 +78,15 @@ void SoftwareRenderNode::render(const RenderState *renderState) p->setTransform(matrix()->toTransform()); p->setOpacity(inheritedOpacity()); - const QPointF p0(m_item->width() - 1, m_item->height() - 1); + const QPointF p0(m_width - 1, m_height - 1); const QPointF p1(0, 0); - const QPointF p2(0, m_item->height() - 1); + const QPointF p2(0, m_height - 1); QPainterPath path(p0); path.lineTo(p1); path.lineTo(p2); path.closeSubpath(); - QLinearGradient gradient(QPointF(0, 0), QPointF(m_item->width(), m_item->height())); + QLinearGradient gradient(QPointF(0, 0), QPointF(m_width, m_height)); gradient.setColorAt(0, Qt::green); gradient.setColorAt(1, Qt::red); @@ -108,5 +105,12 @@ QSGRenderNode::RenderingFlags SoftwareRenderNode::flags() const QRectF SoftwareRenderNode::rect() const { - return QRect(0, 0, m_item->width(), m_item->height()); + return QRect(0, 0, m_width, m_height); +} + +void SoftwareRenderNode::sync(QQuickItem *item) +{ + m_window = item->window(); + m_width = item->width(); + m_height = item->height(); } diff --git a/examples/quick/scenegraph/rendernode/softwarerenderer.h b/examples/quick/scenegraph/rendernode/softwarerenderer.h index cc2aaf7ed3..6091a13ca3 100644 --- a/examples/quick/scenegraph/rendernode/softwarerenderer.h +++ b/examples/quick/scenegraph/rendernode/softwarerenderer.h @@ -57,7 +57,6 @@ class SoftwareRenderNode : public QSGRenderNode { public: - SoftwareRenderNode(QQuickItem *item); ~SoftwareRenderNode(); void render(const RenderState *state) override; @@ -66,8 +65,12 @@ public: RenderingFlags flags() const override; QRectF rect() const override; + void sync(QQuickItem *item); + private: - QQuickItem *m_item; + QQuickWindow *m_window = nullptr; + int m_width = 0; + int m_height = 0; }; #endif diff --git a/examples/quick/scenegraph/scenegraph.pro b/examples/quick/scenegraph/scenegraph.pro index 2efeb5ed83..5fea3b974a 100644 --- a/examples/quick/scenegraph/scenegraph.pro +++ b/examples/quick/scenegraph/scenegraph.pro @@ -5,7 +5,7 @@ qtConfig(opengl(es1|es2)?) { graph \ simplematerial \ sgengine \ - textureinsgnode \ + fboitem \ openglunderqml \ textureinthread \ twotextureproviders @@ -16,5 +16,19 @@ SUBDIRS += \ rendernode \ threadedanimation +macos { + SUBDIRS += \ + metalunderqml \ + metaltextureimport +} + +win32 { + SUBDIRS += d3d11underqml +} + +qtConfig(vulkan) { + SUBDIRS += vulkanunderqml +} + EXAMPLE_FILES += \ shared diff --git a/examples/quick/scenegraph/shared/squircle_rhi.frag b/examples/quick/scenegraph/shared/squircle_rhi.frag new file mode 100644 index 0000000000..8da62b93e6 --- /dev/null +++ b/examples/quick/scenegraph/shared/squircle_rhi.frag @@ -0,0 +1,16 @@ +#version 440 + +layout(location = 0) in vec2 coords; +layout(location = 0) out vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + float t; +} ubuf; + +void main() +{ + float i = 1. - (pow(abs(coords.x), 4.) + pow(abs(coords.y), 4.)); + i = smoothstep(ubuf.t - 0.8, ubuf.t + 0.8, i); + i = floor(i * 20.) / 20.; + fragColor = vec4(coords * .5 + .5, i, i); +} diff --git a/examples/quick/scenegraph/shared/squircle_rhi.vert b/examples/quick/scenegraph/shared/squircle_rhi.vert new file mode 100644 index 0000000000..b57dfdfe10 --- /dev/null +++ b/examples/quick/scenegraph/shared/squircle_rhi.vert @@ -0,0 +1,13 @@ +#version 440 + +layout(location = 0) in vec4 vertices; + +layout(location = 0) out vec2 coords; + +out gl_PerVertex { vec4 gl_Position; }; + +void main() +{ + gl_Position = vertices; + coords = vertices.xy; +} diff --git a/examples/quick/scenegraph/vulkanunderqml/doc/images/vulkanunderqml-example.jpg b/examples/quick/scenegraph/vulkanunderqml/doc/images/vulkanunderqml-example.jpg Binary files differnew file mode 100644 index 0000000000..c3f51b6194 --- /dev/null +++ b/examples/quick/scenegraph/vulkanunderqml/doc/images/vulkanunderqml-example.jpg diff --git a/examples/quick/scenegraph/vulkanunderqml/doc/src/vulkanunderqml.qdoc b/examples/quick/scenegraph/vulkanunderqml/doc/src/vulkanunderqml.qdoc new file mode 100644 index 0000000000..d11982c074 --- /dev/null +++ b/examples/quick/scenegraph/vulkanunderqml/doc/src/vulkanunderqml.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/vulkanunderqml + \title Scene Graph - Vulkan Under QML + \ingroup qtquickexamples + \brief Shows how to render directly with vulkan under a Qt Quick scene. + + \image vulkanunderqml-example.jpg + + The Vulkan Under QML example shows how an application can make use of the + \l QQuickWindow::beforeRendering() and \l + QQuickWindow::beforeRenderPassRecording() signals to draw custom Vulkan + 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 Vulkan + 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 Vulkan commands + are recorded onto the same command buffer that is used by the scene graph. + beforeRendering() on its own is not sufficient for this because it gets + emitted at the start of the frame, before recording the start of a + renderpass instance via + \l{https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/vkCmdBeginRenderPass.html}{vkCmdBeginRenderPass}. + By also connecting to beforeRenderPassRecording(), the application's own + commands and the scene graph's scaffolding will end up in the right order. + + As an alternative, applications that wish to render Vulkan 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 Vulkan + rendering. We animate the threshold value using a NumberAnimation + in the QML file and this value is used by the SPIR-V shader + program that draws the squircles. + + The example is equivalent in most ways to the \l{Scene Graph - OpenGL Under + QML}{OpenGL Under QML}, \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. + + */ diff --git a/examples/quick/scenegraph/vulkanunderqml/main.cpp b/examples/quick/scenegraph/vulkanunderqml/main.cpp new file mode 100644 index 0000000000..a04497b1d6 --- /dev/null +++ b/examples/quick/scenegraph/vulkanunderqml/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 "vulkansquircle.h" + +int main(int argc, char **argv) +{ + QGuiApplication app(argc, argv); + + qmlRegisterType<VulkanSquircle>("VulkanUnderQML", 1, 0, "VulkanSquircle"); + + // This example needs Vulkan. It will not run otherwise. + QQuickWindow::setSceneGraphBackend(QSGRendererInterface::VulkanRhi); + + QQuickView view; + view.setResizeMode(QQuickView::SizeRootObjectToView); + view.setSource(QUrl("qrc:///scenegraph/vulkanunderqml/main.qml")); + view.show(); + + return app.exec(); +} diff --git a/examples/quick/scenegraph/vulkanunderqml/main.qml b/examples/quick/scenegraph/vulkanunderqml/main.qml new file mode 100644 index 0000000000..c364ca7636 --- /dev/null +++ b/examples/quick/scenegraph/vulkanunderqml/main.qml @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +//! [1] +import QtQuick 2.0 +import VulkanUnderQML 1.0 + +Item { + + width: 320 + height: 480 + + VulkanSquircle { + 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 + } + } +//! [1] //! [2] + Rectangle { + color: Qt.rgba(1, 1, 1, 0.7) + radius: 10 + border.width: 1 + border.color: "white" + anchors.fill: label + anchors.margins: -10 + } + + Text { + id: label + color: "black" + wrapMode: Text.WordWrap + text: "The background here is a squircle rendered with raw Vulkan using the beforeRendering() and beforeRenderPassRecording() signals in QQuickWindow. This text label and its border is rendered using QML" + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.margins: 20 + } +} +//! [2] diff --git a/examples/quick/scenegraph/vulkanunderqml/squircle.frag.spv b/examples/quick/scenegraph/vulkanunderqml/squircle.frag.spv Binary files differnew file mode 100644 index 0000000000..e4d13a871d --- /dev/null +++ b/examples/quick/scenegraph/vulkanunderqml/squircle.frag.spv diff --git a/examples/quick/scenegraph/vulkanunderqml/squircle.vert.spv b/examples/quick/scenegraph/vulkanunderqml/squircle.vert.spv Binary files differnew file mode 100644 index 0000000000..5df94a47e4 --- /dev/null +++ b/examples/quick/scenegraph/vulkanunderqml/squircle.vert.spv diff --git a/examples/quick/scenegraph/vulkanunderqml/vulkansquircle.cpp b/examples/quick/scenegraph/vulkanunderqml/vulkansquircle.cpp new file mode 100644 index 0000000000..21f46a25c1 --- /dev/null +++ b/examples/quick/scenegraph/vulkanunderqml/vulkansquircle.cpp @@ -0,0 +1,623 @@ +/**************************************************************************** +** +** 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 "vulkansquircle.h" +#include <QtCore/QRunnable> +#include <QtQuick/QQuickWindow> + +#include <QVulkanInstance> +#include <QVulkanFunctions> + +class SquircleRenderer : public QObject +{ + Q_OBJECT +public: + ~SquircleRenderer(); + + void setT(qreal t) { m_t = t; } + void setViewportSize(const QSize &size) { m_viewportSize = size; } + void setWindow(QQuickWindow *window) { m_window = window; } + +public slots: + void frameStart(); + void mainPassRecordingStart(); + +private: + enum Stage { + VertexStage, + FragmentStage + }; + void prepareShader(Stage stage); + void init(int framesInFlight); + + QSize m_viewportSize; + qreal m_t = 0; + QQuickWindow *m_window; + + QByteArray m_vert; + QByteArray m_frag; + + bool m_initialized = false; + VkPhysicalDevice m_physDev = VK_NULL_HANDLE; + VkDevice m_dev = VK_NULL_HANDLE; + QVulkanDeviceFunctions *m_devFuncs = nullptr; + QVulkanFunctions *m_funcs = nullptr; + + VkBuffer m_vbuf = VK_NULL_HANDLE; + VkDeviceMemory m_vbufMem = VK_NULL_HANDLE; + VkBuffer m_ubuf = VK_NULL_HANDLE; + VkDeviceMemory m_ubufMem = VK_NULL_HANDLE; + VkDeviceSize m_allocPerUbuf = 0; + + VkPipelineCache m_pipelineCache = VK_NULL_HANDLE; + + VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE; + VkDescriptorSetLayout m_resLayout = VK_NULL_HANDLE; + VkPipeline m_pipeline = VK_NULL_HANDLE; + + VkDescriptorPool m_descriptorPool = VK_NULL_HANDLE; + VkDescriptorSet m_ubufDescriptor = VK_NULL_HANDLE; +}; + +VulkanSquircle::VulkanSquircle() +{ + connect(this, &QQuickItem::windowChanged, this, &VulkanSquircle::handleWindowChanged); +} + +void VulkanSquircle::setT(qreal t) +{ + if (t == m_t) + return; + m_t = t; + emit tChanged(); + if (window()) + window()->update(); +} + +void VulkanSquircle::handleWindowChanged(QQuickWindow *win) +{ + if (win) { + connect(win, &QQuickWindow::beforeSynchronizing, this, &VulkanSquircle::sync, Qt::DirectConnection); + connect(win, &QQuickWindow::sceneGraphInvalidated, this, &VulkanSquircle::cleanup, Qt::DirectConnection); + + // Ensure we start with cleared to black. The squircle's blend mode relies on this. + win->setColor(Qt::black); + } +} + +// The safe way to release custom graphics resources is to both connect to +// sceneGraphInvalidated() and implement releaseResources(). To support +// threaded render loops the latter performs the SquircleRenderer destruction +// via scheduleRenderJob(). Note that the VulkanSquircle may be gone by the time +// the QRunnable is invoked. + +void VulkanSquircle::cleanup() +{ + delete m_renderer; + m_renderer = nullptr; +} + +class CleanupJob : public QRunnable +{ +public: + CleanupJob(SquircleRenderer *renderer) : m_renderer(renderer) { } + void run() override { delete m_renderer; } +private: + SquircleRenderer *m_renderer; +}; + +void VulkanSquircle::releaseResources() +{ + window()->scheduleRenderJob(new CleanupJob(m_renderer), QQuickWindow::BeforeSynchronizingStage); + m_renderer = nullptr; +} + +SquircleRenderer::~SquircleRenderer() +{ + qDebug("cleanup"); + if (!m_devFuncs) + return; + + m_devFuncs->vkDestroyPipeline(m_dev, m_pipeline, nullptr); + m_devFuncs->vkDestroyPipelineLayout(m_dev, m_pipelineLayout, nullptr); + m_devFuncs->vkDestroyDescriptorSetLayout(m_dev, m_resLayout, nullptr); + + m_devFuncs->vkDestroyDescriptorPool(m_dev, m_descriptorPool, nullptr); + + m_devFuncs->vkDestroyPipelineCache(m_dev, m_pipelineCache, nullptr); + + m_devFuncs->vkDestroyBuffer(m_dev, m_vbuf, nullptr); + m_devFuncs->vkFreeMemory(m_dev, m_vbufMem, nullptr); + + m_devFuncs->vkDestroyBuffer(m_dev, m_ubuf, nullptr); + m_devFuncs->vkFreeMemory(m_dev, m_ubufMem, nullptr); + + qDebug("released"); +} + +void VulkanSquircle::sync() +{ + if (!m_renderer) { + m_renderer = new SquircleRenderer; + // Initializing resources is done before starting to record the + // renderpass, regardless of wanting an underlay or overlay. + connect(window(), &QQuickWindow::beforeRendering, m_renderer, &SquircleRenderer::frameStart, Qt::DirectConnection); + // Here we want an underlay and therefore connect to + // beforeRenderPassRecording. Changing to afterRenderPassRecording + // would render the squircle on top (overlay). + connect(window(), &QQuickWindow::beforeRenderPassRecording, m_renderer, &SquircleRenderer::mainPassRecordingStart, Qt::DirectConnection); + } + m_renderer->setViewportSize(window()->size() * window()->devicePixelRatio()); + m_renderer->setT(m_t); + m_renderer->setWindow(window()); +} + +void SquircleRenderer::frameStart() +{ + QSGRendererInterface *rif = m_window->rendererInterface(); + + // We are not prepared for anything other than running with the RHI and its Vulkan backend. + Q_ASSERT(rif->graphicsApi() == QSGRendererInterface::VulkanRhi); + + if (m_vert.isEmpty()) + prepareShader(VertexStage); + if (m_frag.isEmpty()) + prepareShader(FragmentStage); + + if (!m_initialized) + init(m_window->graphicsStateInfo().framesInFlight); +} + +static const float vertices[] = { + -1, -1, + 1, -1, + -1, 1, + 1, 1 +}; + +const int UBUF_SIZE = 4; + +void SquircleRenderer::mainPassRecordingStart() +{ + // This example demonstrates the simple case: prepending some commands to + // the scenegraph's main renderpass. It does not create its own passes, + // rendertargets, etc. so no synchronization is needed. + + const QQuickWindow::GraphicsStateInfo &stateInfo(m_window->graphicsStateInfo()); + QSGRendererInterface *rif = m_window->rendererInterface(); + + VkDeviceSize ubufOffset = stateInfo.currentFrameSlot * m_allocPerUbuf; + void *p = nullptr; + VkResult err = m_devFuncs->vkMapMemory(m_dev, m_ubufMem, ubufOffset, m_allocPerUbuf, 0, &p); + if (err != VK_SUCCESS || !p) + qFatal("Failed to map uniform buffer memory: %d", err); + float t = m_t; + memcpy(p, &t, 4); + m_devFuncs->vkUnmapMemory(m_dev, m_ubufMem); + + m_window->beginExternalCommands(); + + // Must query the command buffer _after_ beginExternalCommands(), this is + // actually important when running on Vulkan because what we get here is a + // new secondary command buffer, not the primary one. + VkCommandBuffer cb = *reinterpret_cast<VkCommandBuffer *>( + rif->getResource(m_window, QSGRendererInterface::CommandListResource)); + Q_ASSERT(cb); + + // Do not assume any state persists on the command buffer. (it may be a + // brand new one that just started recording) + + m_devFuncs->vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline); + + VkDeviceSize vbufOffset = 0; + m_devFuncs->vkCmdBindVertexBuffers(cb, 0, 1, &m_vbuf, &vbufOffset); + + uint32_t dynamicOffset = m_allocPerUbuf * stateInfo.currentFrameSlot; + m_devFuncs->vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, + &m_ubufDescriptor, 1, &dynamicOffset); + + VkViewport vp = { 0, 0, float(m_viewportSize.width()), float(m_viewportSize.height()), 0.0f, 1.0f }; + m_devFuncs->vkCmdSetViewport(cb, 0, 1, &vp); + VkRect2D scissor = { { 0, 0 }, { uint32_t(m_viewportSize.width()), uint32_t(m_viewportSize.height()) } }; + m_devFuncs->vkCmdSetScissor(cb, 0, 1, &scissor); + + m_devFuncs->vkCmdDraw(cb, 4, 1, 0, 0); + + m_window->endExternalCommands(); +} + +void SquircleRenderer::prepareShader(Stage stage) +{ + QString filename; + if (stage == VertexStage) { + filename = QLatin1String(":/scenegraph/vulkanunderqml/squircle.vert.spv"); + } else { + Q_ASSERT(stage == FragmentStage); + filename = QLatin1String(":/scenegraph/vulkanunderqml/squircle.frag.spv"); + } + 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()); + } else { + m_frag = contents; + Q_ASSERT(!m_frag.isEmpty()); + } +} + +static inline VkDeviceSize aligned(VkDeviceSize v, VkDeviceSize byteAlign) +{ + return (v + byteAlign - 1) & ~(byteAlign - 1); +} + +void SquircleRenderer::init(int framesInFlight) +{ + qDebug("init"); + + Q_ASSERT(framesInFlight <= 3); + m_initialized = true; + + QSGRendererInterface *rif = m_window->rendererInterface(); + QVulkanInstance *inst = reinterpret_cast<QVulkanInstance *>( + rif->getResource(m_window, QSGRendererInterface::VulkanInstanceResource)); + Q_ASSERT(inst && inst->isValid()); + + m_physDev = *reinterpret_cast<VkPhysicalDevice *>(rif->getResource(m_window, QSGRendererInterface::PhysicalDeviceResource)); + m_dev = *reinterpret_cast<VkDevice *>(rif->getResource(m_window, QSGRendererInterface::DeviceResource)); + Q_ASSERT(m_physDev && m_dev); + + m_devFuncs = inst->deviceFunctions(m_dev); + m_funcs = inst->functions(); + Q_ASSERT(m_devFuncs && m_funcs); + + VkRenderPass rp = *reinterpret_cast<VkRenderPass *>( + rif->getResource(m_window, QSGRendererInterface::RenderPassResource)); + Q_ASSERT(rp); + + // For simplicity we just use host visible buffers instead of device local + staging. + + VkPhysicalDeviceProperties physDevProps; + m_funcs->vkGetPhysicalDeviceProperties(m_physDev, &physDevProps); + + VkPhysicalDeviceMemoryProperties physDevMemProps; + m_funcs->vkGetPhysicalDeviceMemoryProperties(m_physDev, &physDevMemProps); + + VkBufferCreateInfo bufferInfo; + memset(&bufferInfo, 0, sizeof(bufferInfo)); + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = sizeof(vertices); + bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + VkResult err = m_devFuncs->vkCreateBuffer(m_dev, &bufferInfo, nullptr, &m_vbuf); + if (err != VK_SUCCESS) + qFatal("Failed to create vertex buffer: %d", err); + + VkMemoryRequirements memReq; + m_devFuncs->vkGetBufferMemoryRequirements(m_dev, m_vbuf, &memReq); + VkMemoryAllocateInfo allocInfo; + memset(&allocInfo, 0, sizeof(allocInfo)); + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memReq.size; + + uint32_t memTypeIndex = uint32_t(-1); + const VkMemoryType *memType = physDevMemProps.memoryTypes; + for (uint32_t i = 0; i < physDevMemProps.memoryTypeCount; ++i) { + if (memReq.memoryTypeBits & (1 << i)) { + if ((memType[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) + && (memType[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) + { + memTypeIndex = i; + break; + } + } + } + if (memTypeIndex == uint32_t(-1)) + qFatal("Failed to find host visible and coherent memory type"); + + allocInfo.memoryTypeIndex = memTypeIndex; + err = m_devFuncs->vkAllocateMemory(m_dev, &allocInfo, nullptr, &m_vbufMem); + if (err != VK_SUCCESS) + qFatal("Failed to allocate vertex buffer memory of size %u: %d", uint(allocInfo.allocationSize), err); + + void *p = nullptr; + err = m_devFuncs->vkMapMemory(m_dev, m_vbufMem, 0, allocInfo.allocationSize, 0, &p); + if (err != VK_SUCCESS || !p) + qFatal("Failed to map vertex buffer memory: %d", err); + memcpy(p, vertices, sizeof(vertices)); + m_devFuncs->vkUnmapMemory(m_dev, m_vbufMem); + err = m_devFuncs->vkBindBufferMemory(m_dev, m_vbuf, m_vbufMem, 0); + if (err != VK_SUCCESS) + qFatal("Failed to bind vertex buffer memory: %d", err); + + // Now have a uniform buffer with enough space for the buffer data for each + // (potentially) in-flight frame. (as we will write the contents every + // frame, and so would need to wait for command buffer completion if there + // was only one, and that would not be nice) + + // Could have three buffers and three descriptor sets, or one buffer and + // one descriptor set and dynamic offset. We chose the latter in this + // example. + + // We use one memory allocation for all uniform buffers, but then have to + // watch out for the buffer offset aligment requirement, which may be as + // large as 256 bytes. + + m_allocPerUbuf = aligned(UBUF_SIZE, physDevProps.limits.minUniformBufferOffsetAlignment); + + bufferInfo.size = framesInFlight * m_allocPerUbuf; + bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + err = m_devFuncs->vkCreateBuffer(m_dev, &bufferInfo, nullptr, &m_ubuf); + if (err != VK_SUCCESS) + qFatal("Failed to create uniform buffer: %d", err); + m_devFuncs->vkGetBufferMemoryRequirements(m_dev, m_ubuf, &memReq); + memTypeIndex = -1; + for (uint32_t i = 0; i < physDevMemProps.memoryTypeCount; ++i) { + if (memReq.memoryTypeBits & (1 << i)) { + if ((memType[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) + && (memType[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) + { + memTypeIndex = i; + break; + } + } + } + if (memTypeIndex == uint32_t(-1)) + qFatal("Failed to find host visible and coherent memory type"); + + allocInfo.allocationSize = framesInFlight * m_allocPerUbuf; + allocInfo.memoryTypeIndex = memTypeIndex; + err = m_devFuncs->vkAllocateMemory(m_dev, &allocInfo, nullptr, &m_ubufMem); + if (err != VK_SUCCESS) + qFatal("Failed to allocate uniform buffer memory of size %u: %d", uint(allocInfo.allocationSize), err); + + err = m_devFuncs->vkBindBufferMemory(m_dev, m_ubuf, m_ubufMem, 0); + if (err != VK_SUCCESS) + qFatal("Failed to bind uniform buffer memory: %d", err); + + // Now onto the pipeline. + + VkPipelineCacheCreateInfo pipelineCacheInfo; + memset(&pipelineCacheInfo, 0, sizeof(pipelineCacheInfo)); + pipelineCacheInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; + err = m_devFuncs->vkCreatePipelineCache(m_dev, &pipelineCacheInfo, nullptr, &m_pipelineCache); + if (err != VK_SUCCESS) + qFatal("Failed to create pipeline cache: %d", err); + + VkDescriptorSetLayoutBinding descLayoutBinding; + memset(&descLayoutBinding, 0, sizeof(descLayoutBinding)); + descLayoutBinding.binding = 0; + descLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; + descLayoutBinding.descriptorCount = 1; + descLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; + VkDescriptorSetLayoutCreateInfo layoutInfo; + memset(&layoutInfo, 0, sizeof(layoutInfo)); + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = 1; + layoutInfo.pBindings = &descLayoutBinding; + err = m_devFuncs->vkCreateDescriptorSetLayout(m_dev, &layoutInfo, nullptr, &m_resLayout); + if (err != VK_SUCCESS) + qFatal("Failed to create descriptor set layout: %d", err); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo; + memset(&pipelineLayoutInfo, 0, sizeof(pipelineLayoutInfo)); + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 1; + pipelineLayoutInfo.pSetLayouts = &m_resLayout; + err = m_devFuncs->vkCreatePipelineLayout(m_dev, &pipelineLayoutInfo, nullptr, &m_pipelineLayout); + if (err != VK_SUCCESS) + qWarning("Failed to create pipeline layout: %d", err); + + VkGraphicsPipelineCreateInfo pipelineInfo; + memset(&pipelineInfo, 0, sizeof(pipelineInfo)); + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + + VkShaderModuleCreateInfo shaderInfo; + memset(&shaderInfo, 0, sizeof(shaderInfo)); + shaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + shaderInfo.codeSize = m_vert.size(); + shaderInfo.pCode = reinterpret_cast<const quint32 *>(m_vert.constData()); + VkShaderModule vertShaderModule; + err = m_devFuncs->vkCreateShaderModule(m_dev, &shaderInfo, nullptr, &vertShaderModule); + if (err != VK_SUCCESS) + qFatal("Failed to create vertex shader module: %d", err); + + shaderInfo.codeSize = m_frag.size(); + shaderInfo.pCode = reinterpret_cast<const quint32 *>(m_frag.constData()); + VkShaderModule fragShaderModule; + err = m_devFuncs->vkCreateShaderModule(m_dev, &shaderInfo, nullptr, &fragShaderModule); + if (err != VK_SUCCESS) + qFatal("Failed to create fragment shader module: %d", err); + + VkPipelineShaderStageCreateInfo stageInfo[2]; + memset(&stageInfo, 0, sizeof(stageInfo)); + stageInfo[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stageInfo[0].stage = VK_SHADER_STAGE_VERTEX_BIT; + stageInfo[0].module = vertShaderModule; + stageInfo[0].pName = "main"; + stageInfo[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stageInfo[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; + stageInfo[1].module = fragShaderModule; + stageInfo[1].pName = "main"; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = stageInfo; + + VkVertexInputBindingDescription vertexBinding = { + 0, // binding + 2 * sizeof(float), // stride + VK_VERTEX_INPUT_RATE_VERTEX + }; + VkVertexInputAttributeDescription vertexAttr = { + 0, // location + 0, // binding + VK_FORMAT_R32G32_SFLOAT, // 'vertices' only has 2 floats per vertex + 0 // offset + }; + VkPipelineVertexInputStateCreateInfo vertexInputInfo; + memset(&vertexInputInfo, 0, sizeof(vertexInputInfo)); + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.pVertexBindingDescriptions = &vertexBinding; + vertexInputInfo.vertexAttributeDescriptionCount = 1; + vertexInputInfo.pVertexAttributeDescriptions = &vertexAttr; + pipelineInfo.pVertexInputState = &vertexInputInfo; + + VkDynamicState dynStates[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + VkPipelineDynamicStateCreateInfo dynamicInfo; + memset(&dynamicInfo, 0, sizeof(dynamicInfo)); + dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicInfo.dynamicStateCount = 2; + dynamicInfo.pDynamicStates = dynStates; + pipelineInfo.pDynamicState = &dynamicInfo; + + VkPipelineViewportStateCreateInfo viewportInfo; + memset(&viewportInfo, 0, sizeof(viewportInfo)); + viewportInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportInfo.viewportCount = viewportInfo.scissorCount = 1; + pipelineInfo.pViewportState = &viewportInfo; + + VkPipelineInputAssemblyStateCreateInfo iaInfo; + memset(&iaInfo, 0, sizeof(iaInfo)); + iaInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + iaInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + pipelineInfo.pInputAssemblyState = &iaInfo; + + VkPipelineRasterizationStateCreateInfo rsInfo; + memset(&rsInfo, 0, sizeof(rsInfo)); + rsInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rsInfo.lineWidth = 1.0f; + pipelineInfo.pRasterizationState = &rsInfo; + + VkPipelineMultisampleStateCreateInfo msInfo; + memset(&msInfo, 0, sizeof(msInfo)); + msInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + msInfo.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + pipelineInfo.pMultisampleState = &msInfo; + + VkPipelineDepthStencilStateCreateInfo dsInfo; + memset(&dsInfo, 0, sizeof(dsInfo)); + dsInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + pipelineInfo.pDepthStencilState = &dsInfo; + + // SrcAlpha, One + VkPipelineColorBlendStateCreateInfo blendInfo; + memset(&blendInfo, 0, sizeof(blendInfo)); + blendInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + VkPipelineColorBlendAttachmentState blend; + memset(&blend, 0, sizeof(blend)); + blend.blendEnable = true; + blend.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + blend.dstColorBlendFactor = VK_BLEND_FACTOR_ONE; + blend.colorBlendOp = VK_BLEND_OP_ADD; + blend.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + blend.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + blend.alphaBlendOp = VK_BLEND_OP_ADD; + blend.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT + | VK_COLOR_COMPONENT_A_BIT; + blendInfo.attachmentCount = 1; + blendInfo.pAttachments = &blend; + pipelineInfo.pColorBlendState = &blendInfo; + + pipelineInfo.layout = m_pipelineLayout; + + pipelineInfo.renderPass = rp; + + err = m_devFuncs->vkCreateGraphicsPipelines(m_dev, m_pipelineCache, 1, &pipelineInfo, nullptr, &m_pipeline); + + m_devFuncs->vkDestroyShaderModule(m_dev, vertShaderModule, nullptr); + m_devFuncs->vkDestroyShaderModule(m_dev, fragShaderModule, nullptr); + + if (err != VK_SUCCESS) + qFatal("Failed to create graphics pipeline: %d", err); + + // Now just need some descriptors. + VkDescriptorPoolSize descPoolSizes[] = { + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1 } + }; + VkDescriptorPoolCreateInfo descPoolInfo; + memset(&descPoolInfo, 0, sizeof(descPoolInfo)); + descPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + descPoolInfo.flags = 0; // won't use vkFreeDescriptorSets + descPoolInfo.maxSets = 1; + descPoolInfo.poolSizeCount = sizeof(descPoolSizes) / sizeof(descPoolSizes[0]); + descPoolInfo.pPoolSizes = descPoolSizes; + err = m_devFuncs->vkCreateDescriptorPool(m_dev, &descPoolInfo, nullptr, &m_descriptorPool); + if (err != VK_SUCCESS) + qFatal("Failed to create descriptor pool: %d", err); + + VkDescriptorSetAllocateInfo descAllocInfo; + memset(&descAllocInfo, 0, sizeof(descAllocInfo)); + descAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + descAllocInfo.descriptorPool = m_descriptorPool; + descAllocInfo.descriptorSetCount = 1; + descAllocInfo.pSetLayouts = &m_resLayout; + err = m_devFuncs->vkAllocateDescriptorSets(m_dev, &descAllocInfo, &m_ubufDescriptor); + if (err != VK_SUCCESS) + qFatal("Failed to allocate descriptor set"); + + VkWriteDescriptorSet writeInfo; + memset(&writeInfo, 0, sizeof(writeInfo)); + writeInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writeInfo.dstSet = m_ubufDescriptor; + writeInfo.dstBinding = 0; + writeInfo.descriptorCount = 1; + writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; + VkDescriptorBufferInfo bufInfo; + bufInfo.buffer = m_ubuf; + bufInfo.offset = 0; // dynamic offset is used so this is ignored + bufInfo.range = UBUF_SIZE; + writeInfo.pBufferInfo = &bufInfo; + m_devFuncs->vkUpdateDescriptorSets(m_dev, 1, &writeInfo, 0, nullptr); +} + +#include "vulkansquircle.moc" diff --git a/examples/quick/scenegraph/vulkanunderqml/vulkansquircle.h b/examples/quick/scenegraph/vulkanunderqml/vulkansquircle.h new file mode 100644 index 0000000000..7e65d01a15 --- /dev/null +++ b/examples/quick/scenegraph/vulkanunderqml/vulkansquircle.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** 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 VULKANSQUIRCLE_H +#define VULKANSQUIRCLE_H + +#include <QtQuick/QQuickItem> + +class SquircleRenderer; + +class VulkanSquircle : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(qreal t READ t WRITE setT NOTIFY tChanged) + +public: + VulkanSquircle(); + + qreal t() const { return m_t; } + void setT(qreal t); + +signals: + void tChanged(); + +public slots: + void sync(); + void cleanup(); + +private slots: + void handleWindowChanged(QQuickWindow *win); + +private: + void releaseResources() override; + + qreal m_t = 0; + SquircleRenderer *m_renderer = nullptr; +}; + +#endif diff --git a/examples/quick/scenegraph/vulkanunderqml/vulkanunderqml.pro b/examples/quick/scenegraph/vulkanunderqml/vulkanunderqml.pro new file mode 100644 index 0000000000..9ea57b91c3 --- /dev/null +++ b/examples/quick/scenegraph/vulkanunderqml/vulkanunderqml.pro @@ -0,0 +1,10 @@ +!qtConfig(vulkan): error("This example requires Qt built with Vulkan support") + +QT += qml quick + +HEADERS += vulkansquircle.h +SOURCES += vulkansquircle.cpp main.cpp +RESOURCES += vulkanunderqml.qrc + +target.path = $$[QT_INSTALL_EXAMPLES]/quick/scenegraph/vulkanunderqml +INSTALLS += target diff --git a/examples/quick/scenegraph/vulkanunderqml/vulkanunderqml.qrc b/examples/quick/scenegraph/vulkanunderqml/vulkanunderqml.qrc new file mode 100644 index 0000000000..c85be0f238 --- /dev/null +++ b/examples/quick/scenegraph/vulkanunderqml/vulkanunderqml.qrc @@ -0,0 +1,7 @@ +<RCC> + <qresource prefix="/scenegraph/vulkanunderqml"> + <file>main.qml</file> + <file>squircle.vert.spv</file> + <file>squircle.frag.spv</file> + </qresource> +</RCC> |