1 /****************************************************************************
2 **
3 ** Copyright (C) 2019 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the demonstration applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:BSD$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** BSD License Usage
18 ** Alternatively, you may use this file under the terms of the BSD license
19 ** as follows:
20 **
21 ** "Redistribution and use in source and binary forms, with or without
22 ** modification, are permitted provided that the following conditions are
23 ** met:
24 **   * Redistributions of source code must retain the above copyright
25 **     notice, this list of conditions and the following disclaimer.
26 **   * Redistributions in binary form must reproduce the above copyright
27 **     notice, this list of conditions and the following disclaimer in
28 **     the documentation and/or other materials provided with the
29 **     distribution.
30 **   * Neither the name of The Qt Company Ltd nor the names of its
31 **     contributors may be used to endorse or promote products derived
32 **     from this software without specific prior written permission.
33 **
34 **
35 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46 **
47 ** $QT_END_LICENSE$
48 **
49 ****************************************************************************/
50 
51 #include "d3d11squircle.h"
52 #include <QtCore/QFile>
53 #include <QtCore/QRunnable>
54 #include <QtQuick/QQuickWindow>
55 
56 #include <d3d11.h>
57 #include <d3dcompiler.h>
58 
59 class SquircleRenderer : public QObject
60 {
61     Q_OBJECT
62 public:
63     SquircleRenderer();
64     ~SquircleRenderer();
65 
setT(qreal t)66     void setT(qreal t) { m_t = t; }
setViewportSize(const QSize & size)67     void setViewportSize(const QSize &size) { m_viewportSize = size; }
setWindow(QQuickWindow * window)68     void setWindow(QQuickWindow *window) { m_window = window; }
69 
70 public slots:
71     void frameStart();
72     void mainPassRecordingStart();
73 
74 private:
75     enum Stage {
76         VertexStage,
77         FragmentStage
78     };
79     void prepareShader(Stage stage);
80     QByteArray compileShader(Stage stage,
81                              const QByteArray &source,
82                              const QByteArray &entryPoint);
83     void init();
84 
85     QSize m_viewportSize;
86     qreal m_t;
87     QQuickWindow *m_window;
88 
89     ID3D11Device *m_device = nullptr;
90     ID3D11DeviceContext *m_context = nullptr;
91     QByteArray m_vert;
92     QByteArray m_vertEntryPoint;
93     QByteArray m_frag;
94     QByteArray m_fragEntryPoint;
95 
96     bool m_initialized = false;
97     ID3D11Buffer *m_vbuf = nullptr;
98     ID3D11Buffer *m_cbuf = nullptr;
99     ID3D11VertexShader *m_vs = nullptr;
100     ID3D11PixelShader *m_ps = nullptr;
101     ID3D11InputLayout *m_inputLayout = nullptr;
102     ID3D11RasterizerState *m_rastState = nullptr;
103     ID3D11DepthStencilState *m_dsState = nullptr;
104     ID3D11BlendState *m_blendState = nullptr;
105 };
106 
D3D11Squircle()107 D3D11Squircle::D3D11Squircle()
108     : m_t(0)
109     , m_renderer(nullptr)
110 {
111     connect(this, &QQuickItem::windowChanged, this, &D3D11Squircle::handleWindowChanged);
112 }
113 
setT(qreal t)114 void D3D11Squircle::setT(qreal t)
115 {
116     if (t == m_t)
117         return;
118     m_t = t;
119     emit tChanged();
120     if (window())
121         window()->update();
122 }
123 
handleWindowChanged(QQuickWindow * win)124 void D3D11Squircle::handleWindowChanged(QQuickWindow *win)
125 {
126     if (win) {
127         connect(win, &QQuickWindow::beforeSynchronizing, this, &D3D11Squircle::sync, Qt::DirectConnection);
128         connect(win, &QQuickWindow::sceneGraphInvalidated, this, &D3D11Squircle::cleanup, Qt::DirectConnection);
129 
130         // Ensure we start with cleared to black. The squircle's blend mode relies on this.
131         win->setColor(Qt::black);
132     }
133 }
134 
SquircleRenderer()135 SquircleRenderer::SquircleRenderer()
136     : m_t(0)
137 {
138 }
139 
140 // The safe way to release custom graphics resources it to both connect to
141 // sceneGraphInvalidated() and implement releaseResources(). To support
142 // threaded render loops the latter performs the SquircleRenderer destruction
143 // via scheduleRenderJob(). Note that the D3D11Squircle may be gone by the time
144 // the QRunnable is invoked.
145 
cleanup()146 void D3D11Squircle::cleanup()
147 {
148     delete m_renderer;
149     m_renderer = nullptr;
150 }
151 
152 class CleanupJob : public QRunnable
153 {
154 public:
CleanupJob(SquircleRenderer * renderer)155     CleanupJob(SquircleRenderer *renderer) : m_renderer(renderer) { }
run()156     void run() override { delete m_renderer; }
157 private:
158     SquircleRenderer *m_renderer;
159 };
160 
releaseResources()161 void D3D11Squircle::releaseResources()
162 {
163     window()->scheduleRenderJob(new CleanupJob(m_renderer), QQuickWindow::BeforeSynchronizingStage);
164     m_renderer = nullptr;
165 }
166 
~SquircleRenderer()167 SquircleRenderer::~SquircleRenderer()
168 {
169     qDebug("cleanup");
170 
171     if (m_vs)
172         m_vs->Release();
173 
174     if (m_ps)
175         m_ps->Release();
176 
177     if (m_vbuf)
178         m_vbuf->Release();
179 
180     if (m_cbuf)
181         m_cbuf->Release();
182 
183     if (m_inputLayout)
184         m_inputLayout->Release();
185 
186     if (m_rastState)
187         m_rastState->Release();
188 
189     if (m_dsState)
190         m_dsState->Release();
191 
192     if (m_blendState)
193         m_blendState->Release();
194 }
195 
sync()196 void D3D11Squircle::sync()
197 {
198     if (!m_renderer) {
199         m_renderer = new SquircleRenderer;
200         connect(window(), &QQuickWindow::beforeRendering, m_renderer, &SquircleRenderer::frameStart, Qt::DirectConnection);
201         connect(window(), &QQuickWindow::beforeRenderPassRecording, m_renderer, &SquircleRenderer::mainPassRecordingStart, Qt::DirectConnection);
202     }
203     m_renderer->setViewportSize(window()->size() * window()->devicePixelRatio());
204     m_renderer->setT(m_t);
205     m_renderer->setWindow(window());
206 }
207 
frameStart()208 void SquircleRenderer::frameStart()
209 {
210     QSGRendererInterface *rif = m_window->rendererInterface();
211 
212     // We are not prepared for anything other than running with the RHI and its D3D11 backend.
213     Q_ASSERT(rif->graphicsApi() == QSGRendererInterface::Direct3D11Rhi);
214 
215     m_device = reinterpret_cast<ID3D11Device *>(rif->getResource(m_window, QSGRendererInterface::DeviceResource));
216     Q_ASSERT(m_device);
217     m_context = reinterpret_cast<ID3D11DeviceContext *>(rif->getResource(m_window, QSGRendererInterface::DeviceContextResource));
218     Q_ASSERT(m_context);
219 
220     if (m_vert.isEmpty())
221         prepareShader(VertexStage);
222     if (m_frag.isEmpty())
223         prepareShader(FragmentStage);
224 
225     if (!m_initialized)
226         init();
227 }
228 
229 static const float vertices[] = {
230     -1, -1,
231     1, -1,
232     -1, 1,
233     1, 1
234 };
235 
mainPassRecordingStart()236 void SquircleRenderer::mainPassRecordingStart()
237 {
238     m_window->beginExternalCommands();
239 
240     D3D11_MAPPED_SUBRESOURCE mp;
241     // will copy the entire constant buffer every time -> pass WRITE_DISCARD -> prevent pipeline stalls
242     HRESULT hr = m_context->Map(m_cbuf, 0, D3D11_MAP_WRITE_DISCARD, 0, &mp);
243     if (SUCCEEDED(hr)) {
244         float t = m_t;
245         memcpy(mp.pData, &t, 4);
246         m_context->Unmap(m_cbuf, 0);
247     } else {
248         qFatal("Failed to map constant buffer: 0x%x", hr);
249     }
250 
251     D3D11_VIEWPORT v;
252     v.TopLeftX = 0;
253     v.TopLeftY = 0;
254     v.Width = m_viewportSize.width();
255     v.Height = m_viewportSize.height();
256     v.MinDepth = 0;
257     v.MaxDepth = 1;
258     m_context->RSSetViewports(1, &v);
259 
260     m_context->VSSetShader(m_vs, nullptr, 0);
261     m_context->PSSetShader(m_ps, nullptr, 0);
262     m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
263     m_context->IASetInputLayout(m_inputLayout);
264     m_context->OMSetDepthStencilState(m_dsState, 0);
265     float blendConstants[] = { 1, 1, 1, 1 };
266     m_context->OMSetBlendState(m_blendState, blendConstants, 0xFFFFFFFF);
267     m_context->RSSetState(m_rastState);
268 
269     const UINT stride = 2 * sizeof(float); // vec2
270     const UINT offset = 0;
271     m_context->IASetVertexBuffers(0, 1, &m_vbuf, &stride, &offset);
272     m_context->PSSetConstantBuffers(0, 1, &m_cbuf);
273 
274     m_context->Draw(4, 0);
275 
276     m_window->endExternalCommands();
277 }
278 
prepareShader(Stage stage)279 void SquircleRenderer::prepareShader(Stage stage)
280 {
281     QString filename;
282     if (stage == VertexStage) {
283         filename = QLatin1String(":/scenegraph/d3d11underqml/squircle.vert");
284     } else {
285         Q_ASSERT(stage == FragmentStage);
286         filename = QLatin1String(":/scenegraph/d3d11underqml/squircle.frag");
287     }
288     QFile f(filename);
289     if (!f.open(QIODevice::ReadOnly))
290         qFatal("Failed to read shader %s", qPrintable(filename));
291 
292     const QByteArray contents = f.readAll();
293 
294     if (stage == VertexStage) {
295         m_vert = contents;
296         Q_ASSERT(!m_vert.isEmpty());
297         m_vertEntryPoint = QByteArrayLiteral("main");
298     } else {
299         m_frag = contents;
300         Q_ASSERT(!m_frag.isEmpty());
301         m_fragEntryPoint = QByteArrayLiteral("main");
302     }
303 }
304 
compileShader(Stage stage,const QByteArray & source,const QByteArray & entryPoint)305 QByteArray SquircleRenderer::compileShader(Stage stage,
306                                            const QByteArray &source,
307                                            const QByteArray &entryPoint)
308 {
309     const char *target;
310     switch (stage) {
311     case VertexStage:
312         target = "vs_5_0";
313         break;
314     case FragmentStage:
315         target = "ps_5_0";
316         break;
317     default:
318         qFatal("Unknown shader stage %d", stage);
319         return QByteArray();
320     }
321 
322     ID3DBlob *bytecode = nullptr;
323     ID3DBlob *errors = nullptr;
324     HRESULT hr = D3DCompile(source.constData(), source.size(),
325                             nullptr, nullptr, nullptr,
326                             entryPoint.constData(), target, 0, 0, &bytecode, &errors);
327     if (FAILED(hr) || !bytecode) {
328         qWarning("HLSL shader compilation failed: 0x%x", uint(hr));
329         if (errors) {
330             const QByteArray msg(static_cast<const char *>(errors->GetBufferPointer()),
331                                  errors->GetBufferSize());
332             errors->Release();
333             qWarning("%s", msg.constData());
334         }
335         return QByteArray();
336     }
337 
338     QByteArray result;
339     result.resize(bytecode->GetBufferSize());
340     memcpy(result.data(), bytecode->GetBufferPointer(), result.size());
341     bytecode->Release();
342 
343     return result;
344 }
345 
init()346 void SquircleRenderer::init()
347 {
348     qDebug("init");
349     m_initialized = true;
350 
351     const QByteArray vs = compileShader(VertexStage, m_vert, m_vertEntryPoint);
352     const QByteArray fs = compileShader(FragmentStage, m_frag, m_fragEntryPoint);
353 
354     HRESULT hr = m_device->CreateVertexShader(vs.constData(), vs.size(), nullptr, &m_vs);
355     if (FAILED(hr))
356         qFatal("Failed to create vertex shader: 0x%x", hr);
357 
358     hr = m_device->CreatePixelShader(fs.constData(), fs.size(), nullptr, &m_ps);
359     if (FAILED(hr))
360         qFatal("Failed to create pixel shader: 0x%x", hr);
361 
362     D3D11_BUFFER_DESC bufDesc;
363     memset(&bufDesc, 0, sizeof(bufDesc));
364     bufDesc.ByteWidth = sizeof(vertices);
365     bufDesc.Usage = D3D11_USAGE_DEFAULT;
366     bufDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
367     hr = m_device->CreateBuffer(&bufDesc, nullptr, &m_vbuf);
368     if (FAILED(hr))
369         qFatal("Failed to create buffer: 0x%x", hr);
370 
371     m_context->UpdateSubresource(m_vbuf, 0, nullptr, vertices, 0, 0);
372 
373     bufDesc.ByteWidth = 256;
374     bufDesc.Usage = D3D11_USAGE_DYNAMIC;
375     bufDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
376     bufDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
377     hr = m_device->CreateBuffer(&bufDesc, nullptr, &m_cbuf);
378     if (FAILED(hr))
379         qFatal("Failed to create buffer: 0x%x", hr);
380 
381     D3D11_INPUT_ELEMENT_DESC inputDesc;
382     memset(&inputDesc, 0, sizeof(inputDesc));
383     // the output from SPIRV-Cross uses TEXCOORD<location> as the semantic
384     inputDesc.SemanticName = "TEXCOORD";
385     inputDesc.SemanticIndex = 0;
386     inputDesc.Format = DXGI_FORMAT_R32G32_FLOAT; // vec2
387     inputDesc.InputSlot = 0;
388     inputDesc.AlignedByteOffset = 0;
389     inputDesc.InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
390     hr = m_device->CreateInputLayout(&inputDesc, 1, vs.constData(), vs.size(), &m_inputLayout);
391     if (FAILED(hr))
392         qFatal("Failed to create input layout: 0x%x", hr);
393 
394     D3D11_RASTERIZER_DESC rastDesc;
395     memset(&rastDesc, 0, sizeof(rastDesc));
396     rastDesc.FillMode = D3D11_FILL_SOLID;
397     rastDesc.CullMode = D3D11_CULL_NONE;
398     hr = m_device->CreateRasterizerState(&rastDesc, &m_rastState);
399     if (FAILED(hr))
400         qFatal("Failed to create rasterizer state: 0x%x", hr);
401 
402     D3D11_DEPTH_STENCIL_DESC dsDesc;
403     memset(&dsDesc, 0, sizeof(dsDesc));
404     hr = m_device->CreateDepthStencilState(&dsDesc, &m_dsState);
405     if (FAILED(hr))
406         qFatal("Failed to create depth/stencil state: 0x%x", hr);
407 
408     D3D11_BLEND_DESC blendDesc;
409     memset(&blendDesc, 0, sizeof(blendDesc));
410     blendDesc.IndependentBlendEnable = true;
411     D3D11_RENDER_TARGET_BLEND_DESC blend;
412     memset(&blend, 0, sizeof(blend));
413     blend.BlendEnable = true;
414     blend.SrcBlend = D3D11_BLEND_SRC_ALPHA;
415     blend.DestBlend = D3D11_BLEND_ONE;
416     blend.BlendOp = D3D11_BLEND_OP_ADD;
417     blend.SrcBlendAlpha = D3D11_BLEND_SRC_ALPHA;
418     blend.DestBlendAlpha = D3D11_BLEND_ONE;
419     blend.BlendOpAlpha = D3D11_BLEND_OP_ADD;
420     blend.RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
421     blendDesc.RenderTarget[0] = blend;
422     hr = m_device->CreateBlendState(&blendDesc, &m_blendState);
423     if (FAILED(hr))
424         qFatal("Failed to create blend state: 0x%x", hr);
425 }
426 
427 #include "d3d11squircle.moc"
428