/**************************************************************************** ** ** Copyright (C) 2020 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 "window.h" #include #include #include #include #include #include #include #include #include #include "quad.vert.inc" #include "quad.frag.inc" // In this example the Qt Quick scene will always render at 720p regardless of // the window size. const int QML_WIDTH = 1280; const int QML_HEIGHT = 720; // Set to 4 or 8 to enable MSAA. This will lead to creating a multisample // texture, passing in the sample count to Qt Quick (so it sets the graphics // pipelines up as appropriate), and then doing a resolve to a non-multisample // texture every time Quick has rendered its content. const int SAMPLE_COUNT = 1; // by subclassing QQuickRenderControl we gain the ability to report a QWindow // to which certain operations, such as the querying of devicePixelRatio() // should be redirected class RenderControl : public QQuickRenderControl { public: RenderControl(QWindow *w) : m_window(w) { } QWindow *renderWindow(QPoint *offset) override; private: QWindow *m_window; }; QWindow *RenderControl::renderWindow(QPoint *offset) { if (offset) *offset = QPoint(0, 0); return m_window; } Window::Window(Engine *engine) : m_engine(engine) { setSurfaceType(QSurface::OpenGLSurface); m_renderControl = new RenderControl(this); // Whenever something changed in the Quick scene, or rendering was // requested via others means (e.g. QQuickWindow::update()), it should // trigger rendering into the texture when preparing the next frame. connect(m_renderControl, &QQuickRenderControl::renderRequested, this, [this] { m_quickDirty = true; }); connect(m_renderControl, &QQuickRenderControl::sceneChanged, this, [this] { m_quickDirty = true; }); // Note that on its own this is not sufficient to get MSAA, the render // target (the texture in this case) must be set up accordingly as well, // and the sample count also needs to be passed to // QQuickRenderTarget::fromNativeTexture(). m_renderControl->setSamples(SAMPLE_COUNT); // Create a QQuickWindow that is associated with out render control. Note that this // window never gets created or shown, meaning that it will never get an underlying // native (platform) window. m_quickWindow = new QQuickWindow(m_renderControl); m_qmlEngine = new QQmlEngine; m_qmlComponent = new QQmlComponent(m_qmlEngine, QUrl(QLatin1String("qrc:/rendercontrol/demo.qml"))); if (m_qmlComponent->isError()) { for (const QQmlError &error : m_qmlComponent->errors()) qWarning() << error.url() << error.line() << error; } QObject *rootObject = m_qmlComponent->create(); if (m_qmlComponent->isError()) { for (const QQmlError &error : m_qmlComponent->errors()) qWarning() << error.url() << error.line() << error; } QQuickItem *rootItem = qobject_cast(rootObject); rootItem->setSize(QSize(QML_WIDTH, QML_HEIGHT)); m_quickWindow->contentItem()->setSize(rootItem->size()); m_quickWindow->setGeometry(0, 0, rootItem->width(), rootItem->height()); rootItem->setParentItem(m_quickWindow->contentItem()); } Window::~Window() { delete m_qmlComponent; delete m_qmlEngine; delete m_quickWindow; delete m_renderControl; releaseResources(); // Often a no-op (if we already got SurfaceAboutToBeDestroyed), but there // are cases when that's not sent. m_swapchain.destroy(); } // Expose (and UpdateRequest) are all the events we need: resize and screen dpr // changes are handled implicitly since every render() checks for size changes // so no separate event handlers are needed for that. void Window::exposeEvent(QExposeEvent *) { if (isExposed()) { // initialize if this is the first expose if (!m_swapchain.swapchain) m_swapchain = m_engine->createSwapchain(this); // must always render and present a frame on expose if (!size().isEmpty()) render(); } } // Input is severly limited in this example: there is no mapping or projection // of any kind, so it all behaves as if the Qt Quick content was covering the // entire window. The example only cares about button down/up, not the position // so this is acceptable here. void Window::mousePressEvent(QMouseEvent *e) { QMouseEvent mappedEvent(e->type(), e->localPos(), e->screenPos(), e->button(), e->buttons(), e->modifiers()); QCoreApplication::sendEvent(m_quickWindow, &mappedEvent); } void Window::mouseReleaseEvent(QMouseEvent *e) { QMouseEvent mappedEvent(e->type(), e->localPos(), e->screenPos(), e->button(), e->buttons(), e->modifiers()); QCoreApplication::sendEvent(m_quickWindow, &mappedEvent); } bool Window::event(QEvent *e) { switch (e->type()) { case QEvent::UpdateRequest: render(); break; case QEvent::PlatformSurface: // trying to be nice, not strictly required for D3D if (static_cast(e)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) m_swapchain.destroy(); break; default: break; } return QWindow::event(e); } bool Window::initResources() { ID3D11Device *dev = m_engine->device(); // vertex and pixel shader to render a textured quad HRESULT hr = dev->CreateVertexShader(g_quad_vs_main, sizeof(g_quad_vs_main), nullptr, &m_res.vertexShader); if (FAILED(hr)) { qWarning("Failed to create vertex shader: %s", qPrintable(comErrorMessage(hr))); return false; } hr = dev->CreatePixelShader(g_quad_ps_main, sizeof(g_quad_ps_main), nullptr, &m_res.pixelShader); if (FAILED(hr)) { qWarning("Failed to create pixel shader: %s", qPrintable(comErrorMessage(hr))); return false; } // texture into which Qt Quick will render and which we will then sample D3D11_TEXTURE2D_DESC texDesc = {}; texDesc.Width = QML_WIDTH; texDesc.Height = QML_HEIGHT; texDesc.MipLevels = 1; texDesc.ArraySize = 1; texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; if (SAMPLE_COUNT > 1) { texDesc.SampleDesc.Count = SAMPLE_COUNT; texDesc.SampleDesc.Quality = UINT(D3D11_STANDARD_MULTISAMPLE_PATTERN); } else { texDesc.SampleDesc.Count = 1; } // we have to use BIND_SHADER_RESOURCE even if the texture is MSAA because // an SRV may still get created internally by Qt Quick texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; texDesc.Usage = D3D11_USAGE_DEFAULT; hr = dev->CreateTexture2D(&texDesc, nullptr, &m_res.texture); if (FAILED(hr)) { qWarning("Failed to create texture: %s", qPrintable(comErrorMessage(hr))); return false; } if (SAMPLE_COUNT > 1) { texDesc.SampleDesc.Count = 1; texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE; hr = dev->CreateTexture2D(&texDesc, nullptr, &m_res.resolveTexture); if (FAILED(hr)) { qWarning("Failed to create resolve texture: %s", qPrintable(comErrorMessage(hr))); return false; } } D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; srvDesc.Texture2D.MipLevels = 1; hr = dev->CreateShaderResourceView(SAMPLE_COUNT > 1 ? m_res.resolveTexture : m_res.texture, &srvDesc, &m_res.textureSrv); if (FAILED(hr)) { qWarning("Failed to create srv: %s", qPrintable(comErrorMessage(hr))); return false; } D3D11_SAMPLER_DESC sampDesc = {}; sampDesc.Filter = D3D11_FILTER_MIN_POINT_MAG_MIP_LINEAR; sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; sampDesc.MaxAnisotropy = 1.0f; hr = dev->CreateSamplerState(&sampDesc, &m_res.sampler); if (FAILED(hr)) { qWarning("Failed to create sampler state: %s", qPrintable(comErrorMessage(hr))); return false; } m_res.valid = true; return true; } void Window::releaseResources() { RELEASE(m_res.vertexShader); RELEASE(m_res.pixelShader); RELEASE(m_res.texture); RELEASE(m_res.resolveTexture); RELEASE(m_res.textureSrv); RELEASE(m_res.sampler); m_res.valid = false; } void Window::render() { if (!isExposed() || !m_swapchain.swapchain || !m_swapchain.tex || !m_swapchain.rtv) return; // if the window got resized, the swapchain buffers must be resized as well if (m_swapchain.pixelSize != m_engine->swapchainSizeForWindow(this)) m_engine->resizeSwapchain(&m_swapchain, this); if (!m_res.valid) { if (!initResources()) return; } // get some content into m_res.texture from Qt Quick updateQuick(); // now onto our own drawing, targeting the window ID3D11DeviceContext1 *ctx = m_engine->context(); const QSize viewSize = m_swapchain.pixelSize; const float clearColor[] = { 0.4f, 0.7f, 0.0f, 1.0f }; ctx->ClearRenderTargetView(m_swapchain.rtv, clearColor); ctx->ClearDepthStencilView(m_swapchain.dsv, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0); ctx->OMSetRenderTargets(1, &m_swapchain.rtv, m_swapchain.dsv); const D3D11_VIEWPORT viewport = { 0.0f, 0.0f, float(viewSize.width()), float(viewSize.height()), 0.f, 1.0f }; ctx->RSSetViewports(1, &viewport); // draw a textured quad ctx->VSSetShader(m_res.vertexShader, nullptr, 0); ctx->PSSetShader(m_res.pixelShader, nullptr, 0); ctx->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); ctx->IASetInputLayout(nullptr); ctx->OMSetDepthStencilState(nullptr, 0); ctx->OMSetBlendState(nullptr, nullptr, 0xffffffff); ctx->RSSetState(nullptr); ctx->PSSetShaderResources(0, 1, &m_res.textureSrv); ctx->PSSetSamplers(0, 1, &m_res.sampler); ctx->Draw(6, 0); m_swapchain.swapchain->Present(1, 0); requestUpdate(); // will lead to eventually getting a QEvent::UpdateRequest } void Window::updateQuick() { if (!m_quickDirty) return; m_quickDirty = false; if (!m_quickInitialized) { // In addition to setGraphicsApi(), we need a call to // setGraphicsDevice to tell Qt Quick what ID3D11Device(Context) to use // (i.e. we want it to use ours, not to create new ones). m_quickWindow->setGraphicsDevice(QQuickGraphicsDevice::fromDeviceAndContext(m_engine->device(), m_engine->context())); // Now we can kick off the scenegraph. if (!m_renderControl->initialize()) qWarning("Failed to initialize redirected Qt Quick rendering"); // Redirect Qt Quick's output. m_quickWindow->setRenderTarget(QQuickRenderTarget::fromD3D11Texture(m_res.texture, QSize(QML_WIDTH, QML_HEIGHT), SAMPLE_COUNT)); m_quickInitialized = true; } m_renderControl->polishItems(); m_renderControl->beginFrame(); m_renderControl->sync(); m_renderControl->render(); m_renderControl->endFrame(); // Qt Quick's rendering commands are submitted to the device context here if (SAMPLE_COUNT > 1) m_engine->context()->ResolveSubresource(m_res.resolveTexture, 0, m_res.texture, 0, DXGI_FORMAT_R8G8B8A8_UNORM); }