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 = ±
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