1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtQuick module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
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 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qsgd3d12renderer_p.h"
41 #include "qsgd3d12rendercontext_p.h"
42 #include <private/qsgnodeupdater_p.h>
43 #include <private/qsgrendernode_p.h>
44 
45 #include "vs_stencilclip.hlslh"
46 #include "ps_stencilclip.hlslh"
47 
48 //#define I_LIKE_STENCIL
49 
50 QT_BEGIN_NAMESPACE
51 
52 #define QSGNODE_TRAVERSE(NODE) for (QSGNode *child = NODE->firstChild(); child; child = child->nextSibling())
53 
54 // NOTE: Avoid categorized logging. It is slow.
55 
56 #define DECLARE_DEBUG_VAR(variable) \
57     static bool debug_ ## variable() \
58     { static bool value = qgetenv("QSG_RENDERER_DEBUG").contains(QT_STRINGIFY(variable)); return value; }
59 
60 DECLARE_DEBUG_VAR(build)
61 DECLARE_DEBUG_VAR(change)
62 DECLARE_DEBUG_VAR(render)
63 
64 class DummyUpdater : public QSGNodeUpdater
65 {
66 public:
updateState(QSGNode *)67     void updateState(QSGNode *) { };
68 };
69 
QSGD3D12Renderer(QSGRenderContext * context)70 QSGD3D12Renderer::QSGD3D12Renderer(QSGRenderContext *context)
71     : QSGRenderer(context),
72       m_vboData(1024),
73       m_iboData(256),
74       m_cboData(4096),
75       m_renderList(16)
76 {
77     setNodeUpdater(new DummyUpdater);
78 }
79 
~QSGD3D12Renderer()80 QSGD3D12Renderer::~QSGD3D12Renderer()
81 {
82     if (m_engine) {
83         m_engine->releaseBuffer(m_vertexBuf);
84         m_engine->releaseBuffer(m_indexBuf);
85         m_engine->releaseBuffer(m_constantBuf);
86     }
87 }
88 
renderScene(GLuint fboId)89 void QSGD3D12Renderer::renderScene(GLuint fboId)
90 {
91     m_renderTarget = fboId;
92 
93     struct DummyBindable : public QSGBindable {
94         void bind() const { }
95     } bindable;
96 
97     QSGRenderer::renderScene(bindable); // calls back render()
98 }
99 
100 // Search through the node set and remove nodes that are descendants of other
101 // nodes in the same set.
qsg_removeDescendants(const QSet<QSGNode * > & nodes,QSGRootNode * root)102 static QSet<QSGNode *> qsg_removeDescendants(const QSet<QSGNode *> &nodes, QSGRootNode *root)
103 {
104     QSet<QSGNode *> result = nodes;
105     for (QSGNode *node : nodes) {
106         QSGNode *n = node;
107         while (n != root) {
108             if (n != node && result.contains(n)) {
109                 result.remove(node);
110                 break;
111             }
112             n = n->parent();
113         }
114     }
115     return result;
116 }
117 
updateMatrices(QSGNode * node,QSGTransformNode * xform)118 void QSGD3D12Renderer::updateMatrices(QSGNode *node, QSGTransformNode *xform)
119 {
120     if (node->isSubtreeBlocked())
121         return;
122 
123     if (node->type() == QSGNode::TransformNodeType) {
124         QSGTransformNode *tn = static_cast<QSGTransformNode *>(node);
125         if (xform)
126             tn->setCombinedMatrix(xform->combinedMatrix() * tn->matrix());
127         else
128             tn->setCombinedMatrix(tn->matrix());
129         QSGNODE_TRAVERSE(node)
130             updateMatrices(child, tn);
131     } else {
132         if (node->type() == QSGNode::GeometryNodeType || node->type() == QSGNode::ClipNodeType) {
133             m_nodeDirtyMap[node] |= QSGD3D12MaterialRenderState::DirtyMatrix;
134             QSGBasicGeometryNode *gnode = static_cast<QSGBasicGeometryNode *>(node);
135             const QMatrix4x4 *newMatrix = xform ? &xform->combinedMatrix() : nullptr;
136             // NB the newMatrix ptr is usually the same as before as it just
137             // references the transform node's own matrix.
138             gnode->setRendererMatrix(newMatrix);
139         }
140         QSGNODE_TRAVERSE(node)
141             updateMatrices(child, xform);
142     }
143 }
144 
updateOpacities(QSGNode * node,float inheritedOpacity)145 void QSGD3D12Renderer::updateOpacities(QSGNode *node, float inheritedOpacity)
146 {
147     if (node->isSubtreeBlocked())
148         return;
149 
150     if (node->type() == QSGNode::OpacityNodeType) {
151         QSGOpacityNode *on = static_cast<QSGOpacityNode *>(node);
152         float combined = inheritedOpacity * on->opacity();
153         on->setCombinedOpacity(combined);
154         QSGNODE_TRAVERSE(node)
155             updateOpacities(child, combined);
156     } else {
157         if (node->type() == QSGNode::GeometryNodeType) {
158             m_nodeDirtyMap[node] |= QSGD3D12MaterialRenderState::DirtyOpacity;
159             QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node);
160             gn->setInheritedOpacity(inheritedOpacity);
161         }
162         QSGNODE_TRAVERSE(node)
163             updateOpacities(child, inheritedOpacity);
164     }
165 }
166 
buildRenderList(QSGNode * node,QSGClipNode * clip)167 void QSGD3D12Renderer::buildRenderList(QSGNode *node, QSGClipNode *clip)
168 {
169     if (node->isSubtreeBlocked())
170         return;
171 
172     if (node->type() == QSGNode::GeometryNodeType || node->type() == QSGNode::ClipNodeType) {
173         QSGBasicGeometryNode *gn = static_cast<QSGBasicGeometryNode *>(node);
174         QSGGeometry *g = gn->geometry();
175 
176         Element e;
177         e.node = gn;
178 
179         if (g->vertexCount() > 0) {
180             e.vboOffset = m_vboData.size();
181             const int vertexSize = g->sizeOfVertex() * g->vertexCount();
182             m_vboData.resize(m_vboData.size() + vertexSize);
183             memcpy(m_vboData.data() + e.vboOffset, g->vertexData(), vertexSize);
184         }
185 
186         if (g->indexCount() > 0) {
187             e.iboOffset = m_iboData.size();
188             e.iboStride = g->sizeOfIndex();
189             const int indexSize = e.iboStride * g->indexCount();
190             m_iboData.resize(m_iboData.size() + indexSize);
191             memcpy(m_iboData.data() + e.iboOffset, g->indexData(), indexSize);
192         }
193 
194         e.cboOffset = m_cboData.size();
195         if (node->type() == QSGNode::GeometryNodeType) {
196             QSGD3D12Material *m = static_cast<QSGD3D12Material *>(static_cast<QSGGeometryNode *>(node)->activeMaterial());
197             e.cboSize = m->constantBufferSize();
198         } else {
199             // Stencil-based clipping needs a 4x4 matrix.
200             e.cboSize = QSGD3D12Engine::alignedConstantBufferSize(16 * sizeof(float));
201         }
202         m_cboData.resize(m_cboData.size() + e.cboSize);
203 
204         m_renderList.add(e);
205 
206         gn->setRendererClipList(clip);
207         if (node->type() == QSGNode::ClipNodeType)
208             clip = static_cast<QSGClipNode *>(node);
209     } else if (node->type() == QSGNode::RenderNodeType) {
210         QSGRenderNode *rn = static_cast<QSGRenderNode *>(node);
211         Element e;
212         e.node = rn;
213         m_renderList.add(e);
214     }
215 
216     QSGNODE_TRAVERSE(node)
217         buildRenderList(child, clip);
218 }
219 
render()220 void QSGD3D12Renderer::render()
221 {
222     QSGD3D12RenderContext *rc = static_cast<QSGD3D12RenderContext *>(context());
223     m_engine = rc->engine();
224     if (!m_layerRenderer)
225         m_engine->beginFrame();
226     else
227         m_engine->beginLayer();
228 
229     m_activeScissorRect = QRect();
230 
231     if (m_rebuild) {
232         m_rebuild = false;
233 
234         m_dirtyTransformNodes.clear();
235         m_dirtyTransformNodes.insert(rootNode());
236         m_dirtyOpacityNodes.clear();
237         m_dirtyOpacityNodes.insert(rootNode());
238 
239         m_renderList.reset();
240         m_vboData.reset();
241         m_iboData.reset();
242         m_cboData.reset();
243 
244         buildRenderList(rootNode(), nullptr);
245 
246         if (!m_vertexBuf)
247             m_vertexBuf = m_engine->genBuffer();
248         m_engine->resetBuffer(m_vertexBuf, m_vboData.data(), m_vboData.size());
249 
250         if (!m_constantBuf)
251             m_constantBuf = m_engine->genBuffer();
252         m_engine->resetBuffer(m_constantBuf, m_cboData.data(), m_cboData.size());
253 
254         if (m_iboData.size()) {
255             if (!m_indexBuf)
256                 m_indexBuf = m_engine->genBuffer();
257             m_engine->resetBuffer(m_indexBuf, m_iboData.data(), m_iboData.size());
258         } else if (m_indexBuf) {
259             m_engine->releaseBuffer(m_indexBuf);
260             m_indexBuf = 0;
261         }
262 
263         if (Q_UNLIKELY(debug_build())) {
264             qDebug("renderList: %d elements in total", m_renderList.size());
265             for (int i = 0; i < m_renderList.size(); ++i) {
266                 const Element &e = m_renderList.at(i);
267                 qDebug() << " - " << e.vboOffset << e.iboOffset << e.cboOffset << e.cboSize << e.node;
268             }
269         }
270     }
271 
272     const QRect devRect = deviceRect();
273     m_projectionChangedDueToDeviceSize = devRect != m_lastDeviceRect;
274     if (m_projectionChangedDueToDeviceSize)
275         m_lastDeviceRect = devRect;
276 
277     if (m_dirtyTransformNodes.size()) {
278         const QSet<QSGNode *> subTreeRoots = qsg_removeDescendants(m_dirtyTransformNodes, rootNode());
279         for (QSGNode *node : subTreeRoots) {
280             // First find the parent transform so we have the accumulated
281             // matrix up until this point.
282             QSGTransformNode *xform = 0;
283             QSGNode *n = node;
284             if (n->type() == QSGNode::TransformNodeType)
285                 n = node->parent();
286             while (n != rootNode() && n->type() != QSGNode::TransformNodeType)
287                 n = n->parent();
288             if (n != rootNode())
289                 xform = static_cast<QSGTransformNode *>(n);
290 
291             // Then update in the subtree
292             updateMatrices(node, xform);
293         }
294     }
295 
296     if (m_dirtyOpacityNodes.size()) {
297         const QSet<QSGNode *> subTreeRoots = qsg_removeDescendants(m_dirtyOpacityNodes, rootNode());
298         for (QSGNode *node : subTreeRoots) {
299             float opacity = 1.0f;
300             QSGNode *n = node;
301             if (n->type() == QSGNode::OpacityNodeType)
302                 n = node->parent();
303             while (n != rootNode() && n->type() != QSGNode::OpacityNodeType)
304                 n = n->parent();
305             if (n != rootNode())
306                 opacity = static_cast<QSGOpacityNode *>(n)->combinedOpacity();
307 
308             updateOpacities(node, opacity);
309         }
310         m_dirtyOpaqueElements = true;
311     }
312 
313     if (m_dirtyOpaqueElements) {
314         m_dirtyOpaqueElements = false;
315         m_opaqueElements.clear();
316         m_opaqueElements.resize(m_renderList.size());
317         for (int i = 0; i < m_renderList.size(); ++i) {
318             const Element &e = m_renderList.at(i);
319             if (e.node->type() == QSGNode::GeometryNodeType) {
320                 const QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(e.node);
321                 if (gn->inheritedOpacity() > 0.999f && ((gn->activeMaterial()->flags() & QSGMaterial::Blending) == 0))
322                     m_opaqueElements.setBit(i);
323             }
324             // QSGRenderNodes are always treated as non-opaque
325         }
326     }
327 
328     // Build pipeline state and draw calls.
329     renderElements();
330 
331     m_dirtyTransformNodes.clear();
332     m_dirtyOpacityNodes.clear();
333     m_dirtyOpaqueElements = false;
334     m_nodeDirtyMap.clear();
335 
336     // Finalize buffers and execute commands.
337     if (!m_layerRenderer)
338         m_engine->endFrame();
339     else
340         m_engine->endLayer();
341 }
342 
nodeChanged(QSGNode * node,QSGNode::DirtyState state)343 void QSGD3D12Renderer::nodeChanged(QSGNode *node, QSGNode::DirtyState state)
344 {
345     // note that with DirtyNodeRemoved the window and all the graphics engine may already be gone
346 
347     if (Q_UNLIKELY(debug_change())) {
348         QDebug debug = qDebug();
349         debug << "dirty:";
350         if (state & QSGNode::DirtyGeometry)
351             debug << "Geometry";
352         if (state & QSGNode::DirtyMaterial)
353             debug << "Material";
354         if (state & QSGNode::DirtyMatrix)
355             debug << "Matrix";
356         if (state & QSGNode::DirtyNodeAdded)
357             debug << "Added";
358         if (state & QSGNode::DirtyNodeRemoved)
359             debug << "Removed";
360         if (state & QSGNode::DirtyOpacity)
361             debug << "Opacity";
362         if (state & QSGNode::DirtySubtreeBlocked)
363             debug << "SubtreeBlocked";
364         if (state & QSGNode::DirtyForceUpdate)
365             debug << "ForceUpdate";
366 
367         // when removed, some parts of the node could already have been destroyed
368         // so don't debug it out.
369         if (state & QSGNode::DirtyNodeRemoved)
370             debug << (void *) node << node->type();
371         else
372             debug << node;
373     }
374 
375     if (state & (QSGNode::DirtyNodeAdded
376                  | QSGNode::DirtyNodeRemoved
377                  | QSGNode::DirtySubtreeBlocked
378                  | QSGNode::DirtyGeometry
379                  | QSGNode::DirtyForceUpdate))
380         m_rebuild = true;
381 
382     if (state & QSGNode::DirtyMatrix)
383         m_dirtyTransformNodes << node;
384 
385     if (state & QSGNode::DirtyOpacity)
386         m_dirtyOpacityNodes << node;
387 
388     if (state & QSGNode::DirtyMaterial)
389         m_dirtyOpaqueElements = true;
390 
391     QSGRenderer::nodeChanged(node, state);
392 }
393 
renderElements()394 void QSGD3D12Renderer::renderElements()
395 {
396     m_engine->queueSetRenderTarget(m_renderTarget);
397     m_engine->queueViewport(viewportRect());
398     m_engine->queueClearRenderTarget(clearColor());
399     m_engine->queueClearDepthStencil(1, 0, QSGD3D12Engine::ClearDepth | QSGD3D12Engine::ClearStencil);
400 
401     m_pipelineState.blend = m_freshPipelineState.blend = QSGD3D12PipelineState::BlendNone;
402     m_pipelineState.depthEnable = m_freshPipelineState.depthEnable = true;
403     m_pipelineState.depthWrite = m_freshPipelineState.depthWrite = true;
404 
405     // First do opaque...
406     // The algorithm is quite simple. We traverse the list back-to-front, and
407     // for every item we start a second traversal and draw all elements which
408     // have identical material. Then we clear the bit for this in the rendered
409     // list so we don't draw it again when we come to that index.
410     QBitArray rendered = m_opaqueElements;
411     for (int i = m_renderList.size() - 1; i >= 0; --i) {
412         if (rendered.testBit(i)) {
413             renderElement(i);
414             for (int j = i - 1; j >= 0; --j) {
415                 if (rendered.testBit(j)) {
416                     const QSGGeometryNode *gni = static_cast<QSGGeometryNode *>(m_renderList.at(i).node);
417                     const QSGGeometryNode *gnj = static_cast<QSGGeometryNode *>(m_renderList.at(j).node);
418                     if (gni->clipList() == gnj->clipList()
419                             && gni->inheritedOpacity() == gnj->inheritedOpacity()
420                             && gni->geometry()->drawingMode() == gnj->geometry()->drawingMode()
421                             && gni->geometry()->attributes() == gnj->geometry()->attributes()) {
422                         const QSGMaterial *ami = gni->activeMaterial();
423                         const QSGMaterial *amj = gnj->activeMaterial();
424                         if (ami->type() == amj->type()
425                                 && ami->flags() == amj->flags()
426                                 && ami->compare(amj) == 0) {
427                             renderElement(j);
428                             rendered.clearBit(j);
429                         }
430                     }
431                 }
432             }
433         }
434     }
435 
436     m_pipelineState.blend = m_freshPipelineState.blend = QSGD3D12PipelineState::BlendPremul;
437     m_pipelineState.depthWrite = m_freshPipelineState.depthWrite = false;
438 
439     // ...then the alpha ones
440     for (int i = 0; i < m_renderList.size(); ++i) {
441         if ((m_renderList.at(i).node->type() == QSGNode::GeometryNodeType && !m_opaqueElements.testBit(i))
442                 || m_renderList.at(i).node->type() == QSGNode::RenderNodeType)
443             renderElement(i);
444     }
445 }
446 
447 struct RenderNodeState : public QSGRenderNode::RenderState
448 {
projectionMatrixRenderNodeState449     const QMatrix4x4 *projectionMatrix() const override { return m_projectionMatrix; }
scissorRectRenderNodeState450     QRect scissorRect() const override { return m_scissorRect; }
scissorEnabledRenderNodeState451     bool scissorEnabled() const override { return m_scissorEnabled; }
stencilValueRenderNodeState452     int stencilValue() const override { return m_stencilValue; }
stencilEnabledRenderNodeState453     bool stencilEnabled() const override { return m_stencilEnabled; }
clipRegionRenderNodeState454     const QRegion *clipRegion() const override { return nullptr; }
455 
456     const QMatrix4x4 *m_projectionMatrix;
457     QRect m_scissorRect;
458     bool m_scissorEnabled;
459     int m_stencilValue;
460     bool m_stencilEnabled;
461 };
462 
renderElement(int elementIndex)463 void QSGD3D12Renderer::renderElement(int elementIndex)
464 {
465     Element &e = m_renderList.at(elementIndex);
466     Q_ASSERT(e.node->type() == QSGNode::GeometryNodeType || e.node->type() == QSGNode::RenderNodeType);
467 
468     if (e.node->type() == QSGNode::RenderNodeType) {
469         renderRenderNode(static_cast<QSGRenderNode *>(e.node), elementIndex);
470         return;
471     }
472 
473     if (e.vboOffset < 0)
474         return;
475 
476     Q_ASSERT(e.cboOffset >= 0);
477 
478     const QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(e.node);
479     if (Q_UNLIKELY(debug_render()))
480         qDebug() << "renderElement:" << elementIndex << gn << e.vboOffset << e.iboOffset << gn->inheritedOpacity() << gn->clipList();
481 
482     if (gn->inheritedOpacity() < 0.001f) // pretty much invisible, don't draw it
483         return;
484 
485     // Update the QSGRenderer members which the materials will access.
486     m_current_projection_matrix = projectionMatrix();
487     const float scale = 1.0 / m_renderList.size();
488     m_current_projection_matrix(2, 2) = scale;
489     m_current_projection_matrix(2, 3) = 1.0f - (elementIndex + 1) * scale;
490     m_current_model_view_matrix = gn->matrix() ? *gn->matrix() : QMatrix4x4();
491     m_current_determinant = m_current_model_view_matrix.determinant();
492     m_current_opacity = gn->inheritedOpacity();
493 
494     const QSGGeometry *g = gn->geometry();
495     QSGD3D12Material *m = static_cast<QSGD3D12Material *>(gn->activeMaterial());
496 
497     if (m->type() != m_lastMaterialType) {
498         m_pipelineState = m_freshPipelineState;
499         m->preparePipeline(&m_pipelineState);
500     }
501 
502     QSGD3D12MaterialRenderState::DirtyStates dirtyState = m_nodeDirtyMap.value(e.node);
503 
504     // After a rebuild everything in the cbuffer has to be updated.
505     if (!e.cboPrepared) {
506         e.cboPrepared = true;
507         dirtyState = QSGD3D12MaterialRenderState::DirtyAll;
508     }
509 
510     // DirtyMatrix does not include projection matrix changes that can arise
511     // due to changing the render target's size (and there is no rebuild).
512     // Accommodate for this.
513     if (m_projectionChangedDueToDeviceSize)
514         dirtyState |= QSGD3D12MaterialRenderState::DirtyMatrix;
515 
516     quint8 *cboPtr = nullptr;
517     if (e.cboSize > 0)
518         cboPtr = m_cboData.data() + e.cboOffset;
519 
520     if (Q_UNLIKELY(debug_render()))
521         qDebug() << "dirty state for" << e.node << "is" << dirtyState;
522 
523     QSGD3D12Material::ExtraState extraState;
524     QSGD3D12Material::UpdateResults updRes = m->updatePipeline(state(dirtyState),
525                                                                &m_pipelineState,
526                                                                &extraState,
527                                                                cboPtr);
528 
529     if (updRes.testFlag(QSGD3D12Material::UpdatedConstantBuffer))
530         m_engine->markBufferDirty(m_constantBuf, e.cboOffset, e.cboSize);
531 
532     if (updRes.testFlag(QSGD3D12Material::UpdatedBlendFactor))
533         m_engine->queueSetBlendFactor(extraState.blendFactor);
534 
535     setInputLayout(g, &m_pipelineState);
536 
537     m_lastMaterialType = m->type();
538 
539     setupClipping(gn->clipList(), elementIndex);
540 
541     // ### Lines and points with sizes other than 1 have to be implemented in some other way. Just ignore for now.
542     if (g->drawingMode() == QSGGeometry::DrawLineStrip || g->drawingMode() == QSGGeometry::DrawLines) {
543         if (g->lineWidth() != 1.0f)
544             qWarning("QSGD3D12Renderer: Line widths other than 1 are not supported by this renderer");
545     } else if (g->drawingMode() == QSGGeometry::DrawPoints) {
546         if (g->lineWidth() != 1.0f)
547             qWarning("QSGD3D12Renderer: Point sprites are not supported by this renderer");
548     }
549 
550     m_engine->finalizePipeline(m_pipelineState);
551 
552     queueDrawCall(g, e);
553 }
554 
setInputLayout(const QSGGeometry * g,QSGD3D12PipelineState * pipelineState)555 void QSGD3D12Renderer::setInputLayout(const QSGGeometry *g, QSGD3D12PipelineState *pipelineState)
556 {
557     pipelineState->inputElementCount = g->attributeCount();
558     const QSGGeometry::Attribute *attrs = g->attributes();
559     quint32 offset = 0;
560     for (int i = 0; i < g->attributeCount(); ++i) {
561         QSGD3D12InputElement &ie(pipelineState->inputElements[i]);
562         static const char *semanticNames[] = { "UNKNOWN", "POSITION", "COLOR", "TEXCOORD", "TEXCOORD", "TEXCOORD" };
563         static const int semanticIndices[] = { 0, 0, 0, 0, 1, 2 };
564         const int semantic = attrs[i].attributeType;
565         Q_ASSERT(semantic >= 1 && semantic < _countof(semanticNames));
566         const int tupleSize = attrs[i].tupleSize;
567         ie.semanticName = semanticNames[semantic];
568         ie.semanticIndex = semanticIndices[semantic];
569         ie.offset = offset;
570         int bytesPerTuple = 0;
571         ie.format = QSGD3D12Engine::toDXGIFormat(QSGGeometry::Type(attrs[i].type), tupleSize, &bytesPerTuple);
572         if (ie.format == FmtUnknown)
573             qFatal("QSGD3D12Renderer: unsupported tuple size for attribute type 0x%x", attrs[i].type);
574         offset += bytesPerTuple;
575         // There is one buffer with interleaved data so the slot is always 0.
576         ie.slot = 0;
577     }
578 }
579 
queueDrawCall(const QSGGeometry * g,const QSGD3D12Renderer::Element & e)580 void QSGD3D12Renderer::queueDrawCall(const QSGGeometry *g, const QSGD3D12Renderer::Element &e)
581 {
582     QSGD3D12Engine::DrawParams dp;
583     dp.mode = QSGGeometry::DrawingMode(g->drawingMode());
584     dp.vertexBuf = m_vertexBuf;
585     dp.constantBuf = m_constantBuf;
586     dp.vboOffset = e.vboOffset;
587     dp.vboSize = g->vertexCount() * g->sizeOfVertex();
588     dp.vboStride = g->sizeOfVertex();
589     dp.cboOffset = e.cboOffset;
590 
591     if (e.iboOffset >= 0) {
592         const QSGGeometry::Type indexType = QSGGeometry::Type(g->indexType());
593         const QSGD3D12Format indexFormat = QSGD3D12Engine::toDXGIFormat(indexType);
594         if (indexFormat == FmtUnknown)
595             qFatal("QSGD3D12Renderer: unsupported index type 0x%x", indexType);
596         dp.count = g->indexCount();
597         dp.indexBuf = m_indexBuf;
598         dp.startIndexIndex = e.iboOffset / e.iboStride;
599         dp.indexFormat = indexFormat;
600     } else {
601         dp.count = g->vertexCount();
602     }
603 
604     m_engine->queueDraw(dp);
605 }
606 
setupClipping(const QSGClipNode * clip,int elementIndex)607 void QSGD3D12Renderer::setupClipping(const QSGClipNode *clip, int elementIndex)
608 {
609     const QRect devRect = deviceRect();
610     QRect scissorRect;
611     int clipTypes = 0;
612     quint32 stencilValue = 0;
613 
614     while (clip) {
615         QMatrix4x4 m = projectionMatrix();
616         if (clip->matrix())
617             m *= *clip->matrix();
618 
619 #ifndef I_LIKE_STENCIL
620         const bool isRectangleWithNoPerspective = clip->isRectangular()
621                 && qFuzzyIsNull(m(3, 0)) && qFuzzyIsNull(m(3, 1));
622         const bool noRotate = qFuzzyIsNull(m(0, 1)) && qFuzzyIsNull(m(1, 0));
623         const bool isRotate90 = qFuzzyIsNull(m(0, 0)) && qFuzzyIsNull(m(1, 1));
624 
625         if (isRectangleWithNoPerspective && (noRotate || isRotate90)) {
626             QRectF bbox = clip->clipRect();
627             float invW = 1.0f / m(3, 3);
628             float fx1, fy1, fx2, fy2;
629             if (noRotate) {
630                 fx1 = (bbox.left() * m(0, 0) + m(0, 3)) * invW;
631                 fy1 = (bbox.bottom() * m(1, 1) + m(1, 3)) * invW;
632                 fx2 = (bbox.right() * m(0, 0) + m(0, 3)) * invW;
633                 fy2 = (bbox.top() * m(1, 1) + m(1, 3)) * invW;
634             } else {
635                 Q_ASSERT(isRotate90);
636                 fx1 = (bbox.bottom() * m(0, 1) + m(0, 3)) * invW;
637                 fy1 = (bbox.left() * m(1, 0) + m(1, 3)) * invW;
638                 fx2 = (bbox.top() * m(0, 1) + m(0, 3)) * invW;
639                 fy2 = (bbox.right() * m(1, 0) + m(1, 3)) * invW;
640             }
641 
642             if (fx1 > fx2)
643                 qSwap(fx1, fx2);
644             if (fy1 > fy2)
645                 qSwap(fy1, fy2);
646 
647             int ix1 = qRound((fx1 + 1) * devRect.width() * 0.5f);
648             int iy1 = qRound((fy1 + 1) * devRect.height() * 0.5f);
649             int ix2 = qRound((fx2 + 1) * devRect.width() * 0.5f);
650             int iy2 = qRound((fy2 + 1) * devRect.height() * 0.5f);
651 
652             if (!(clipTypes & ClipScissor)) {
653                 scissorRect = QRect(ix1, devRect.height() - iy2, ix2 - ix1, iy2 - iy1);
654                 clipTypes |= ClipScissor;
655             } else {
656                 scissorRect &= QRect(ix1, devRect.height() - iy2, ix2 - ix1, iy2 - iy1);
657             }
658         } else
659 #endif
660         {
661             clipTypes |= ClipStencil;
662             renderStencilClip(clip, elementIndex, m, stencilValue);
663         }
664 
665         clip = clip->clipList();
666     }
667 
668     setScissor((clipTypes & ClipScissor) ? scissorRect : viewportRect());
669 
670     if (clipTypes & ClipStencil) {
671         m_pipelineState.stencilEnable = true;
672         m_engine->queueSetStencilRef(stencilValue);
673         m_currentStencilValue = stencilValue;
674     } else {
675         m_pipelineState.stencilEnable = false;
676         m_currentStencilValue = 0;
677     }
678 
679     m_currentClipTypes = clipTypes;
680 }
681 
setScissor(const QRect & r)682 void QSGD3D12Renderer::setScissor(const QRect &r)
683 {
684     if (m_activeScissorRect == r)
685         return;
686 
687     m_activeScissorRect = r;
688     m_engine->queueScissor(r);
689 }
690 
renderStencilClip(const QSGClipNode * clip,int elementIndex,const QMatrix4x4 & m,quint32 & stencilValue)691 void QSGD3D12Renderer::renderStencilClip(const QSGClipNode *clip, int elementIndex,
692                                          const QMatrix4x4 &m, quint32 &stencilValue)
693 {
694     QSGD3D12PipelineState sps;
695     sps.shaders.vs = g_VS_StencilClip;
696     sps.shaders.vsSize = sizeof(g_VS_StencilClip);
697     sps.shaders.ps = g_PS_StencilClip;
698     sps.shaders.psSize = sizeof(g_PS_StencilClip);
699 
700     m_engine->queueClearDepthStencil(1, 0, QSGD3D12Engine::ClearStencil);
701     sps.stencilEnable = true;
702     sps.colorWrite = false;
703     sps.depthWrite = false;
704 
705     sps.stencilFunc = QSGD3D12PipelineState::CompareEqual;
706     sps.stencilFailOp = QSGD3D12PipelineState::StencilKeep;
707     sps.stencilDepthFailOp = QSGD3D12PipelineState::StencilKeep;
708     sps.stencilPassOp = QSGD3D12PipelineState::StencilIncr;
709 
710     m_engine->queueSetStencilRef(stencilValue);
711 
712     int clipIndex = elementIndex;
713     while (m_renderList.at(--clipIndex).node != clip) {
714         Q_ASSERT(clipIndex >= 0);
715     }
716     const Element &ce = m_renderList.at(clipIndex);
717     Q_ASSERT(ce.node == clip);
718 
719     const QSGGeometry *g = clip->geometry();
720     Q_ASSERT(g->attributeCount() == 1);
721     Q_ASSERT(g->attributes()[0].tupleSize == 2);
722     Q_ASSERT(g->attributes()[0].type == QSGGeometry::FloatType);
723 
724     setInputLayout(g, &sps);
725     m_engine->finalizePipeline(sps);
726 
727     Q_ASSERT(ce.cboSize > 0);
728     quint8 *p = m_cboData.data() + ce.cboOffset;
729     memcpy(p, m.constData(), 16 * sizeof(float));
730     m_engine->markBufferDirty(m_constantBuf, ce.cboOffset, ce.cboSize);
731 
732     queueDrawCall(g, ce);
733 
734     ++stencilValue;
735 }
736 
renderRenderNode(QSGRenderNode * node,int elementIndex)737 void QSGD3D12Renderer::renderRenderNode(QSGRenderNode *node, int elementIndex)
738 {
739     QSGRenderNodePrivate *rd = QSGRenderNodePrivate::get(node);
740     RenderNodeState state;
741 
742     setupClipping(rd->m_clip_list, elementIndex);
743 
744     QMatrix4x4 pm = projectionMatrix();
745     state.m_projectionMatrix = &pm;
746     state.m_scissorEnabled = m_currentClipTypes & ClipScissor;
747     state.m_stencilEnabled = m_currentClipTypes & ClipStencil;
748     state.m_scissorRect = m_activeScissorRect;
749     state.m_stencilValue = m_currentStencilValue;
750 
751     // ### rendernodes do not have the QSGBasicGeometryNode infrastructure
752     // for storing combined matrices, opacity and such, but perhaps they should.
753     QSGNode *xform = node->parent();
754     QSGNode *root = rootNode();
755     QMatrix4x4 modelview;
756     while (xform != root) {
757         if (xform->type() == QSGNode::TransformNodeType) {
758             modelview *= static_cast<QSGTransformNode *>(xform)->combinedMatrix();
759             break;
760         }
761         xform = xform->parent();
762     }
763     rd->m_matrix = &modelview;
764 
765     QSGNode *opacity = node->parent();
766     rd->m_opacity = 1.0;
767     while (opacity != rootNode()) {
768         if (opacity->type() == QSGNode::OpacityNodeType) {
769             rd->m_opacity = static_cast<QSGOpacityNode *>(opacity)->combinedOpacity();
770             break;
771         }
772         opacity = opacity->parent();
773     }
774 
775     node->render(&state);
776 
777     m_engine->invalidateCachedFrameState();
778     // For simplicity, reset viewport, scissor, blend factor, stencil ref when
779     // any of them got changed. This will likely be rare so skip these otherwise.
780     // Render target, pipeline state, draw call related stuff will be reset always.
781     const bool restoreMinimal = node->changedStates() == 0;
782     m_engine->restoreFrameState(restoreMinimal);
783 }
784 
785 QT_END_NAMESPACE
786