/**************************************************************************** ** ** 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 #include #include #include #include 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(rif->getResource(m_window, QSGRendererInterface::DeviceResource)); Q_ASSERT(m_device); m_context = reinterpret_cast(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(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 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"