diff options
Diffstat (limited to 'examples/quick/scenegraph/d3d11underqml/d3d11squircle.cpp')
-rw-r--r-- | examples/quick/scenegraph/d3d11underqml/d3d11squircle.cpp | 426 |
1 files changed, 426 insertions, 0 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" |