aboutsummaryrefslogtreecommitdiffstats
path: root/examples/quick/rendercontrol/rendercontrol_d3d11/window.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'examples/quick/rendercontrol/rendercontrol_d3d11/window.cpp')
-rw-r--r--examples/quick/rendercontrol/rendercontrol_d3d11/window.cpp389
1 files changed, 389 insertions, 0 deletions
diff --git a/examples/quick/rendercontrol/rendercontrol_d3d11/window.cpp b/examples/quick/rendercontrol/rendercontrol_d3d11/window.cpp
new file mode 100644
index 0000000000..cb84371fed
--- /dev/null
+++ b/examples/quick/rendercontrol/rendercontrol_d3d11/window.cpp
@@ -0,0 +1,389 @@
+/****************************************************************************
+**
+** 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 <QCoreApplication>
+#include <QMouseEvent>
+#include <QQuickRenderControl>
+#include <QQuickWindow>
+#include <QQuickItem>
+#include <QQmlEngine>
+#include <QQmlComponent>
+#include <QQuickRenderTarget>
+#include <QQuickGraphicsDevice>
+
+#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<QQuickItem *>(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<QPlatformSurfaceEvent *>(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 setSceneGraphBackend, 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. (note that the QSGTexture::NativeTexture
+ // struct is expected to contain a pointer to the native object, even
+ // if the native object type is a pointer, such as ID3D11Texture2D*)
+ m_quickWindow->setRenderTarget(QQuickRenderTarget::fromNativeTexture({ &m_res.texture, 0 },
+ 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);
+}