1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
|
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#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:/qt/qml/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;
}
m_rootItem = qobject_cast<QQuickItem *>(rootObject);
m_rootItem->setSize(QSize(QML_WIDTH, QML_HEIGHT));
m_quickWindow->contentItem()->setSize(m_rootItem->size());
m_quickWindow->setGeometry(0, 0, m_rootItem->width(), m_rootItem->height());
m_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->position(), e->globalPosition(), e->button(), e->buttons(), e->modifiers());
QCoreApplication::sendEvent(m_quickWindow, &mappedEvent);
}
void Window::mouseReleaseEvent(QMouseEvent *e)
{
QMouseEvent mappedEvent(e->type(), e->position(), e->globalPosition(), e->button(), e->buttons(), e->modifiers());
QCoreApplication::sendEvent(m_quickWindow, &mappedEvent);
}
void Window::keyPressEvent(QKeyEvent *e)
{
QCoreApplication::sendEvent(m_quickWindow, e);
}
void Window::keyReleaseEvent(QKeyEvent *e)
{
QCoreApplication::sendEvent(m_quickWindow, e);
}
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
ID3D11DeviceContext *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));
// Ensure key events are received by the root Rectangle.
m_rootItem->forceActiveFocus();
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);
}
|