| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtQuick module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** 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. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qsgd3d12renderer_p.h" |
| #include "qsgd3d12rendercontext_p.h" |
| #include <private/qsgnodeupdater_p.h> |
| #include <private/qsgrendernode_p.h> |
| |
| #include "vs_stencilclip.hlslh" |
| #include "ps_stencilclip.hlslh" |
| |
| //#define I_LIKE_STENCIL |
| |
| QT_BEGIN_NAMESPACE |
| |
| #define QSGNODE_TRAVERSE(NODE) for (QSGNode *child = NODE->firstChild(); child; child = child->nextSibling()) |
| |
| // NOTE: Avoid categorized logging. It is slow. |
| |
| #define DECLARE_DEBUG_VAR(variable) \ |
| static bool debug_ ## variable() \ |
| { static bool value = qgetenv("QSG_RENDERER_DEBUG").contains(QT_STRINGIFY(variable)); return value; } |
| |
| DECLARE_DEBUG_VAR(build) |
| DECLARE_DEBUG_VAR(change) |
| DECLARE_DEBUG_VAR(render) |
| |
| class DummyUpdater : public QSGNodeUpdater |
| { |
| public: |
| void updateState(QSGNode *) { }; |
| }; |
| |
| QSGD3D12Renderer::QSGD3D12Renderer(QSGRenderContext *context) |
| : QSGRenderer(context), |
| m_vboData(1024), |
| m_iboData(256), |
| m_cboData(4096), |
| m_renderList(16) |
| { |
| setNodeUpdater(new DummyUpdater); |
| } |
| |
| QSGD3D12Renderer::~QSGD3D12Renderer() |
| { |
| if (m_engine) { |
| m_engine->releaseBuffer(m_vertexBuf); |
| m_engine->releaseBuffer(m_indexBuf); |
| m_engine->releaseBuffer(m_constantBuf); |
| } |
| } |
| |
| void QSGD3D12Renderer::renderScene(GLuint fboId) |
| { |
| m_renderTarget = fboId; |
| |
| struct DummyBindable : public QSGBindable { |
| void bind() const { } |
| } bindable; |
| |
| QSGRenderer::renderScene(bindable); // calls back render() |
| } |
| |
| // Search through the node set and remove nodes that are descendants of other |
| // nodes in the same set. |
| static QSet<QSGNode *> qsg_removeDescendants(const QSet<QSGNode *> &nodes, QSGRootNode *root) |
| { |
| QSet<QSGNode *> result = nodes; |
| for (QSGNode *node : nodes) { |
| QSGNode *n = node; |
| while (n != root) { |
| if (n != node && result.contains(n)) { |
| result.remove(node); |
| break; |
| } |
| n = n->parent(); |
| } |
| } |
| return result; |
| } |
| |
| void QSGD3D12Renderer::updateMatrices(QSGNode *node, QSGTransformNode *xform) |
| { |
| if (node->isSubtreeBlocked()) |
| return; |
| |
| if (node->type() == QSGNode::TransformNodeType) { |
| QSGTransformNode *tn = static_cast<QSGTransformNode *>(node); |
| if (xform) |
| tn->setCombinedMatrix(xform->combinedMatrix() * tn->matrix()); |
| else |
| tn->setCombinedMatrix(tn->matrix()); |
| QSGNODE_TRAVERSE(node) |
| updateMatrices(child, tn); |
| } else { |
| if (node->type() == QSGNode::GeometryNodeType || node->type() == QSGNode::ClipNodeType) { |
| m_nodeDirtyMap[node] |= QSGD3D12MaterialRenderState::DirtyMatrix; |
| QSGBasicGeometryNode *gnode = static_cast<QSGBasicGeometryNode *>(node); |
| const QMatrix4x4 *newMatrix = xform ? &xform->combinedMatrix() : nullptr; |
| // NB the newMatrix ptr is usually the same as before as it just |
| // references the transform node's own matrix. |
| gnode->setRendererMatrix(newMatrix); |
| } |
| QSGNODE_TRAVERSE(node) |
| updateMatrices(child, xform); |
| } |
| } |
| |
| void QSGD3D12Renderer::updateOpacities(QSGNode *node, float inheritedOpacity) |
| { |
| if (node->isSubtreeBlocked()) |
| return; |
| |
| if (node->type() == QSGNode::OpacityNodeType) { |
| QSGOpacityNode *on = static_cast<QSGOpacityNode *>(node); |
| float combined = inheritedOpacity * on->opacity(); |
| on->setCombinedOpacity(combined); |
| QSGNODE_TRAVERSE(node) |
| updateOpacities(child, combined); |
| } else { |
| if (node->type() == QSGNode::GeometryNodeType) { |
| m_nodeDirtyMap[node] |= QSGD3D12MaterialRenderState::DirtyOpacity; |
| QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(node); |
| gn->setInheritedOpacity(inheritedOpacity); |
| } |
| QSGNODE_TRAVERSE(node) |
| updateOpacities(child, inheritedOpacity); |
| } |
| } |
| |
| void QSGD3D12Renderer::buildRenderList(QSGNode *node, QSGClipNode *clip) |
| { |
| if (node->isSubtreeBlocked()) |
| return; |
| |
| if (node->type() == QSGNode::GeometryNodeType || node->type() == QSGNode::ClipNodeType) { |
| QSGBasicGeometryNode *gn = static_cast<QSGBasicGeometryNode *>(node); |
| QSGGeometry *g = gn->geometry(); |
| |
| Element e; |
| e.node = gn; |
| |
| if (g->vertexCount() > 0) { |
| e.vboOffset = m_vboData.size(); |
| const int vertexSize = g->sizeOfVertex() * g->vertexCount(); |
| m_vboData.resize(m_vboData.size() + vertexSize); |
| memcpy(m_vboData.data() + e.vboOffset, g->vertexData(), vertexSize); |
| } |
| |
| if (g->indexCount() > 0) { |
| e.iboOffset = m_iboData.size(); |
| e.iboStride = g->sizeOfIndex(); |
| const int indexSize = e.iboStride * g->indexCount(); |
| m_iboData.resize(m_iboData.size() + indexSize); |
| memcpy(m_iboData.data() + e.iboOffset, g->indexData(), indexSize); |
| } |
| |
| e.cboOffset = m_cboData.size(); |
| if (node->type() == QSGNode::GeometryNodeType) { |
| QSGD3D12Material *m = static_cast<QSGD3D12Material *>(static_cast<QSGGeometryNode *>(node)->activeMaterial()); |
| e.cboSize = m->constantBufferSize(); |
| } else { |
| // Stencil-based clipping needs a 4x4 matrix. |
| e.cboSize = QSGD3D12Engine::alignedConstantBufferSize(16 * sizeof(float)); |
| } |
| m_cboData.resize(m_cboData.size() + e.cboSize); |
| |
| m_renderList.add(e); |
| |
| gn->setRendererClipList(clip); |
| if (node->type() == QSGNode::ClipNodeType) |
| clip = static_cast<QSGClipNode *>(node); |
| } else if (node->type() == QSGNode::RenderNodeType) { |
| QSGRenderNode *rn = static_cast<QSGRenderNode *>(node); |
| Element e; |
| e.node = rn; |
| m_renderList.add(e); |
| } |
| |
| QSGNODE_TRAVERSE(node) |
| buildRenderList(child, clip); |
| } |
| |
| void QSGD3D12Renderer::render() |
| { |
| QSGD3D12RenderContext *rc = static_cast<QSGD3D12RenderContext *>(context()); |
| m_engine = rc->engine(); |
| if (!m_layerRenderer) |
| m_engine->beginFrame(); |
| else |
| m_engine->beginLayer(); |
| |
| m_activeScissorRect = QRect(); |
| |
| if (m_rebuild) { |
| m_rebuild = false; |
| |
| m_dirtyTransformNodes.clear(); |
| m_dirtyTransformNodes.insert(rootNode()); |
| m_dirtyOpacityNodes.clear(); |
| m_dirtyOpacityNodes.insert(rootNode()); |
| |
| m_renderList.reset(); |
| m_vboData.reset(); |
| m_iboData.reset(); |
| m_cboData.reset(); |
| |
| buildRenderList(rootNode(), nullptr); |
| |
| if (!m_vertexBuf) |
| m_vertexBuf = m_engine->genBuffer(); |
| m_engine->resetBuffer(m_vertexBuf, m_vboData.data(), m_vboData.size()); |
| |
| if (!m_constantBuf) |
| m_constantBuf = m_engine->genBuffer(); |
| m_engine->resetBuffer(m_constantBuf, m_cboData.data(), m_cboData.size()); |
| |
| if (m_iboData.size()) { |
| if (!m_indexBuf) |
| m_indexBuf = m_engine->genBuffer(); |
| m_engine->resetBuffer(m_indexBuf, m_iboData.data(), m_iboData.size()); |
| } else if (m_indexBuf) { |
| m_engine->releaseBuffer(m_indexBuf); |
| m_indexBuf = 0; |
| } |
| |
| if (Q_UNLIKELY(debug_build())) { |
| qDebug("renderList: %d elements in total", m_renderList.size()); |
| for (int i = 0; i < m_renderList.size(); ++i) { |
| const Element &e = m_renderList.at(i); |
| qDebug() << " - " << e.vboOffset << e.iboOffset << e.cboOffset << e.cboSize << e.node; |
| } |
| } |
| } |
| |
| const QRect devRect = deviceRect(); |
| m_projectionChangedDueToDeviceSize = devRect != m_lastDeviceRect; |
| if (m_projectionChangedDueToDeviceSize) |
| m_lastDeviceRect = devRect; |
| |
| if (m_dirtyTransformNodes.size()) { |
| const QSet<QSGNode *> subTreeRoots = qsg_removeDescendants(m_dirtyTransformNodes, rootNode()); |
| for (QSGNode *node : subTreeRoots) { |
| // First find the parent transform so we have the accumulated |
| // matrix up until this point. |
| QSGTransformNode *xform = 0; |
| QSGNode *n = node; |
| if (n->type() == QSGNode::TransformNodeType) |
| n = node->parent(); |
| while (n != rootNode() && n->type() != QSGNode::TransformNodeType) |
| n = n->parent(); |
| if (n != rootNode()) |
| xform = static_cast<QSGTransformNode *>(n); |
| |
| // Then update in the subtree |
| updateMatrices(node, xform); |
| } |
| } |
| |
| if (m_dirtyOpacityNodes.size()) { |
| const QSet<QSGNode *> subTreeRoots = qsg_removeDescendants(m_dirtyOpacityNodes, rootNode()); |
| for (QSGNode *node : subTreeRoots) { |
| float opacity = 1.0f; |
| QSGNode *n = node; |
| if (n->type() == QSGNode::OpacityNodeType) |
| n = node->parent(); |
| while (n != rootNode() && n->type() != QSGNode::OpacityNodeType) |
| n = n->parent(); |
| if (n != rootNode()) |
| opacity = static_cast<QSGOpacityNode *>(n)->combinedOpacity(); |
| |
| updateOpacities(node, opacity); |
| } |
| m_dirtyOpaqueElements = true; |
| } |
| |
| if (m_dirtyOpaqueElements) { |
| m_dirtyOpaqueElements = false; |
| m_opaqueElements.clear(); |
| m_opaqueElements.resize(m_renderList.size()); |
| for (int i = 0; i < m_renderList.size(); ++i) { |
| const Element &e = m_renderList.at(i); |
| if (e.node->type() == QSGNode::GeometryNodeType) { |
| const QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(e.node); |
| if (gn->inheritedOpacity() > 0.999f && ((gn->activeMaterial()->flags() & QSGMaterial::Blending) == 0)) |
| m_opaqueElements.setBit(i); |
| } |
| // QSGRenderNodes are always treated as non-opaque |
| } |
| } |
| |
| // Build pipeline state and draw calls. |
| renderElements(); |
| |
| m_dirtyTransformNodes.clear(); |
| m_dirtyOpacityNodes.clear(); |
| m_dirtyOpaqueElements = false; |
| m_nodeDirtyMap.clear(); |
| |
| // Finalize buffers and execute commands. |
| if (!m_layerRenderer) |
| m_engine->endFrame(); |
| else |
| m_engine->endLayer(); |
| } |
| |
| void QSGD3D12Renderer::nodeChanged(QSGNode *node, QSGNode::DirtyState state) |
| { |
| // note that with DirtyNodeRemoved the window and all the graphics engine may already be gone |
| |
| if (Q_UNLIKELY(debug_change())) { |
| QDebug debug = qDebug(); |
| debug << "dirty:"; |
| if (state & QSGNode::DirtyGeometry) |
| debug << "Geometry"; |
| if (state & QSGNode::DirtyMaterial) |
| debug << "Material"; |
| if (state & QSGNode::DirtyMatrix) |
| debug << "Matrix"; |
| if (state & QSGNode::DirtyNodeAdded) |
| debug << "Added"; |
| if (state & QSGNode::DirtyNodeRemoved) |
| debug << "Removed"; |
| if (state & QSGNode::DirtyOpacity) |
| debug << "Opacity"; |
| if (state & QSGNode::DirtySubtreeBlocked) |
| debug << "SubtreeBlocked"; |
| if (state & QSGNode::DirtyForceUpdate) |
| debug << "ForceUpdate"; |
| |
| // when removed, some parts of the node could already have been destroyed |
| // so don't debug it out. |
| if (state & QSGNode::DirtyNodeRemoved) |
| debug << (void *) node << node->type(); |
| else |
| debug << node; |
| } |
| |
| if (state & (QSGNode::DirtyNodeAdded |
| | QSGNode::DirtyNodeRemoved |
| | QSGNode::DirtySubtreeBlocked |
| | QSGNode::DirtyGeometry |
| | QSGNode::DirtyForceUpdate)) |
| m_rebuild = true; |
| |
| if (state & QSGNode::DirtyMatrix) |
| m_dirtyTransformNodes << node; |
| |
| if (state & QSGNode::DirtyOpacity) |
| m_dirtyOpacityNodes << node; |
| |
| if (state & QSGNode::DirtyMaterial) |
| m_dirtyOpaqueElements = true; |
| |
| QSGRenderer::nodeChanged(node, state); |
| } |
| |
| void QSGD3D12Renderer::renderElements() |
| { |
| m_engine->queueSetRenderTarget(m_renderTarget); |
| m_engine->queueViewport(viewportRect()); |
| m_engine->queueClearRenderTarget(clearColor()); |
| m_engine->queueClearDepthStencil(1, 0, QSGD3D12Engine::ClearDepth | QSGD3D12Engine::ClearStencil); |
| |
| m_pipelineState.blend = m_freshPipelineState.blend = QSGD3D12PipelineState::BlendNone; |
| m_pipelineState.depthEnable = m_freshPipelineState.depthEnable = true; |
| m_pipelineState.depthWrite = m_freshPipelineState.depthWrite = true; |
| |
| // First do opaque... |
| // The algorithm is quite simple. We traverse the list back-to-front, and |
| // for every item we start a second traversal and draw all elements which |
| // have identical material. Then we clear the bit for this in the rendered |
| // list so we don't draw it again when we come to that index. |
| QBitArray rendered = m_opaqueElements; |
| for (int i = m_renderList.size() - 1; i >= 0; --i) { |
| if (rendered.testBit(i)) { |
| renderElement(i); |
| for (int j = i - 1; j >= 0; --j) { |
| if (rendered.testBit(j)) { |
| const QSGGeometryNode *gni = static_cast<QSGGeometryNode *>(m_renderList.at(i).node); |
| const QSGGeometryNode *gnj = static_cast<QSGGeometryNode *>(m_renderList.at(j).node); |
| if (gni->clipList() == gnj->clipList() |
| && gni->inheritedOpacity() == gnj->inheritedOpacity() |
| && gni->geometry()->drawingMode() == gnj->geometry()->drawingMode() |
| && gni->geometry()->attributes() == gnj->geometry()->attributes()) { |
| const QSGMaterial *ami = gni->activeMaterial(); |
| const QSGMaterial *amj = gnj->activeMaterial(); |
| if (ami->type() == amj->type() |
| && ami->flags() == amj->flags() |
| && ami->compare(amj) == 0) { |
| renderElement(j); |
| rendered.clearBit(j); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| m_pipelineState.blend = m_freshPipelineState.blend = QSGD3D12PipelineState::BlendPremul; |
| m_pipelineState.depthWrite = m_freshPipelineState.depthWrite = false; |
| |
| // ...then the alpha ones |
| for (int i = 0; i < m_renderList.size(); ++i) { |
| if ((m_renderList.at(i).node->type() == QSGNode::GeometryNodeType && !m_opaqueElements.testBit(i)) |
| || m_renderList.at(i).node->type() == QSGNode::RenderNodeType) |
| renderElement(i); |
| } |
| } |
| |
| struct RenderNodeState : public QSGRenderNode::RenderState |
| { |
| const QMatrix4x4 *projectionMatrix() const override { return m_projectionMatrix; } |
| QRect scissorRect() const override { return m_scissorRect; } |
| bool scissorEnabled() const override { return m_scissorEnabled; } |
| int stencilValue() const override { return m_stencilValue; } |
| bool stencilEnabled() const override { return m_stencilEnabled; } |
| const QRegion *clipRegion() const override { return nullptr; } |
| |
| const QMatrix4x4 *m_projectionMatrix; |
| QRect m_scissorRect; |
| bool m_scissorEnabled; |
| int m_stencilValue; |
| bool m_stencilEnabled; |
| }; |
| |
| void QSGD3D12Renderer::renderElement(int elementIndex) |
| { |
| Element &e = m_renderList.at(elementIndex); |
| Q_ASSERT(e.node->type() == QSGNode::GeometryNodeType || e.node->type() == QSGNode::RenderNodeType); |
| |
| if (e.node->type() == QSGNode::RenderNodeType) { |
| renderRenderNode(static_cast<QSGRenderNode *>(e.node), elementIndex); |
| return; |
| } |
| |
| if (e.vboOffset < 0) |
| return; |
| |
| Q_ASSERT(e.cboOffset >= 0); |
| |
| const QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(e.node); |
| if (Q_UNLIKELY(debug_render())) |
| qDebug() << "renderElement:" << elementIndex << gn << e.vboOffset << e.iboOffset << gn->inheritedOpacity() << gn->clipList(); |
| |
| if (gn->inheritedOpacity() < 0.001f) // pretty much invisible, don't draw it |
| return; |
| |
| // Update the QSGRenderer members which the materials will access. |
| m_current_projection_matrix = projectionMatrix(); |
| const float scale = 1.0 / m_renderList.size(); |
| m_current_projection_matrix(2, 2) = scale; |
| m_current_projection_matrix(2, 3) = 1.0f - (elementIndex + 1) * scale; |
| m_current_model_view_matrix = gn->matrix() ? *gn->matrix() : QMatrix4x4(); |
| m_current_determinant = m_current_model_view_matrix.determinant(); |
| m_current_opacity = gn->inheritedOpacity(); |
| |
| const QSGGeometry *g = gn->geometry(); |
| QSGD3D12Material *m = static_cast<QSGD3D12Material *>(gn->activeMaterial()); |
| |
| if (m->type() != m_lastMaterialType) { |
| m_pipelineState = m_freshPipelineState; |
| m->preparePipeline(&m_pipelineState); |
| } |
| |
| QSGD3D12MaterialRenderState::DirtyStates dirtyState = m_nodeDirtyMap.value(e.node); |
| |
| // After a rebuild everything in the cbuffer has to be updated. |
| if (!e.cboPrepared) { |
| e.cboPrepared = true; |
| dirtyState = QSGD3D12MaterialRenderState::DirtyAll; |
| } |
| |
| // DirtyMatrix does not include projection matrix changes that can arise |
| // due to changing the render target's size (and there is no rebuild). |
| // Accommodate for this. |
| if (m_projectionChangedDueToDeviceSize) |
| dirtyState |= QSGD3D12MaterialRenderState::DirtyMatrix; |
| |
| quint8 *cboPtr = nullptr; |
| if (e.cboSize > 0) |
| cboPtr = m_cboData.data() + e.cboOffset; |
| |
| if (Q_UNLIKELY(debug_render())) |
| qDebug() << "dirty state for" << e.node << "is" << dirtyState; |
| |
| QSGD3D12Material::ExtraState extraState; |
| QSGD3D12Material::UpdateResults updRes = m->updatePipeline(state(dirtyState), |
| &m_pipelineState, |
| &extraState, |
| cboPtr); |
| |
| if (updRes.testFlag(QSGD3D12Material::UpdatedConstantBuffer)) |
| m_engine->markBufferDirty(m_constantBuf, e.cboOffset, e.cboSize); |
| |
| if (updRes.testFlag(QSGD3D12Material::UpdatedBlendFactor)) |
| m_engine->queueSetBlendFactor(extraState.blendFactor); |
| |
| setInputLayout(g, &m_pipelineState); |
| |
| m_lastMaterialType = m->type(); |
| |
| setupClipping(gn->clipList(), elementIndex); |
| |
| // ### Lines and points with sizes other than 1 have to be implemented in some other way. Just ignore for now. |
| if (g->drawingMode() == QSGGeometry::DrawLineStrip || g->drawingMode() == QSGGeometry::DrawLines) { |
| if (g->lineWidth() != 1.0f) |
| qWarning("QSGD3D12Renderer: Line widths other than 1 are not supported by this renderer"); |
| } else if (g->drawingMode() == QSGGeometry::DrawPoints) { |
| if (g->lineWidth() != 1.0f) |
| qWarning("QSGD3D12Renderer: Point sprites are not supported by this renderer"); |
| } |
| |
| m_engine->finalizePipeline(m_pipelineState); |
| |
| queueDrawCall(g, e); |
| } |
| |
| void QSGD3D12Renderer::setInputLayout(const QSGGeometry *g, QSGD3D12PipelineState *pipelineState) |
| { |
| pipelineState->inputElementCount = g->attributeCount(); |
| const QSGGeometry::Attribute *attrs = g->attributes(); |
| quint32 offset = 0; |
| for (int i = 0; i < g->attributeCount(); ++i) { |
| QSGD3D12InputElement &ie(pipelineState->inputElements[i]); |
| static const char *semanticNames[] = { "UNKNOWN", "POSITION", "COLOR", "TEXCOORD", "TEXCOORD", "TEXCOORD" }; |
| static const int semanticIndices[] = { 0, 0, 0, 0, 1, 2 }; |
| const int semantic = attrs[i].attributeType; |
| Q_ASSERT(semantic >= 1 && semantic < _countof(semanticNames)); |
| const int tupleSize = attrs[i].tupleSize; |
| ie.semanticName = semanticNames[semantic]; |
| ie.semanticIndex = semanticIndices[semantic]; |
| ie.offset = offset; |
| int bytesPerTuple = 0; |
| ie.format = QSGD3D12Engine::toDXGIFormat(QSGGeometry::Type(attrs[i].type), tupleSize, &bytesPerTuple); |
| if (ie.format == FmtUnknown) |
| qFatal("QSGD3D12Renderer: unsupported tuple size for attribute type 0x%x", attrs[i].type); |
| offset += bytesPerTuple; |
| // There is one buffer with interleaved data so the slot is always 0. |
| ie.slot = 0; |
| } |
| } |
| |
| void QSGD3D12Renderer::queueDrawCall(const QSGGeometry *g, const QSGD3D12Renderer::Element &e) |
| { |
| QSGD3D12Engine::DrawParams dp; |
| dp.mode = QSGGeometry::DrawingMode(g->drawingMode()); |
| dp.vertexBuf = m_vertexBuf; |
| dp.constantBuf = m_constantBuf; |
| dp.vboOffset = e.vboOffset; |
| dp.vboSize = g->vertexCount() * g->sizeOfVertex(); |
| dp.vboStride = g->sizeOfVertex(); |
| dp.cboOffset = e.cboOffset; |
| |
| if (e.iboOffset >= 0) { |
| const QSGGeometry::Type indexType = QSGGeometry::Type(g->indexType()); |
| const QSGD3D12Format indexFormat = QSGD3D12Engine::toDXGIFormat(indexType); |
| if (indexFormat == FmtUnknown) |
| qFatal("QSGD3D12Renderer: unsupported index type 0x%x", indexType); |
| dp.count = g->indexCount(); |
| dp.indexBuf = m_indexBuf; |
| dp.startIndexIndex = e.iboOffset / e.iboStride; |
| dp.indexFormat = indexFormat; |
| } else { |
| dp.count = g->vertexCount(); |
| } |
| |
| m_engine->queueDraw(dp); |
| } |
| |
| void QSGD3D12Renderer::setupClipping(const QSGClipNode *clip, int elementIndex) |
| { |
| const QRect devRect = deviceRect(); |
| QRect scissorRect; |
| int clipTypes = 0; |
| quint32 stencilValue = 0; |
| |
| while (clip) { |
| QMatrix4x4 m = projectionMatrix(); |
| if (clip->matrix()) |
| m *= *clip->matrix(); |
| |
| #ifndef I_LIKE_STENCIL |
| const bool isRectangleWithNoPerspective = clip->isRectangular() |
| && qFuzzyIsNull(m(3, 0)) && qFuzzyIsNull(m(3, 1)); |
| const bool noRotate = qFuzzyIsNull(m(0, 1)) && qFuzzyIsNull(m(1, 0)); |
| const bool isRotate90 = qFuzzyIsNull(m(0, 0)) && qFuzzyIsNull(m(1, 1)); |
| |
| if (isRectangleWithNoPerspective && (noRotate || isRotate90)) { |
| QRectF bbox = clip->clipRect(); |
| float invW = 1.0f / m(3, 3); |
| float fx1, fy1, fx2, fy2; |
| if (noRotate) { |
| fx1 = (bbox.left() * m(0, 0) + m(0, 3)) * invW; |
| fy1 = (bbox.bottom() * m(1, 1) + m(1, 3)) * invW; |
| fx2 = (bbox.right() * m(0, 0) + m(0, 3)) * invW; |
| fy2 = (bbox.top() * m(1, 1) + m(1, 3)) * invW; |
| } else { |
| Q_ASSERT(isRotate90); |
| fx1 = (bbox.bottom() * m(0, 1) + m(0, 3)) * invW; |
| fy1 = (bbox.left() * m(1, 0) + m(1, 3)) * invW; |
| fx2 = (bbox.top() * m(0, 1) + m(0, 3)) * invW; |
| fy2 = (bbox.right() * m(1, 0) + m(1, 3)) * invW; |
| } |
| |
| if (fx1 > fx2) |
| qSwap(fx1, fx2); |
| if (fy1 > fy2) |
| qSwap(fy1, fy2); |
| |
| int ix1 = qRound((fx1 + 1) * devRect.width() * 0.5f); |
| int iy1 = qRound((fy1 + 1) * devRect.height() * 0.5f); |
| int ix2 = qRound((fx2 + 1) * devRect.width() * 0.5f); |
| int iy2 = qRound((fy2 + 1) * devRect.height() * 0.5f); |
| |
| if (!(clipTypes & ClipScissor)) { |
| scissorRect = QRect(ix1, devRect.height() - iy2, ix2 - ix1, iy2 - iy1); |
| clipTypes |= ClipScissor; |
| } else { |
| scissorRect &= QRect(ix1, devRect.height() - iy2, ix2 - ix1, iy2 - iy1); |
| } |
| } else |
| #endif |
| { |
| clipTypes |= ClipStencil; |
| renderStencilClip(clip, elementIndex, m, stencilValue); |
| } |
| |
| clip = clip->clipList(); |
| } |
| |
| setScissor((clipTypes & ClipScissor) ? scissorRect : viewportRect()); |
| |
| if (clipTypes & ClipStencil) { |
| m_pipelineState.stencilEnable = true; |
| m_engine->queueSetStencilRef(stencilValue); |
| m_currentStencilValue = stencilValue; |
| } else { |
| m_pipelineState.stencilEnable = false; |
| m_currentStencilValue = 0; |
| } |
| |
| m_currentClipTypes = clipTypes; |
| } |
| |
| void QSGD3D12Renderer::setScissor(const QRect &r) |
| { |
| if (m_activeScissorRect == r) |
| return; |
| |
| m_activeScissorRect = r; |
| m_engine->queueScissor(r); |
| } |
| |
| void QSGD3D12Renderer::renderStencilClip(const QSGClipNode *clip, int elementIndex, |
| const QMatrix4x4 &m, quint32 &stencilValue) |
| { |
| QSGD3D12PipelineState sps; |
| sps.shaders.vs = g_VS_StencilClip; |
| sps.shaders.vsSize = sizeof(g_VS_StencilClip); |
| sps.shaders.ps = g_PS_StencilClip; |
| sps.shaders.psSize = sizeof(g_PS_StencilClip); |
| |
| m_engine->queueClearDepthStencil(1, 0, QSGD3D12Engine::ClearStencil); |
| sps.stencilEnable = true; |
| sps.colorWrite = false; |
| sps.depthWrite = false; |
| |
| sps.stencilFunc = QSGD3D12PipelineState::CompareEqual; |
| sps.stencilFailOp = QSGD3D12PipelineState::StencilKeep; |
| sps.stencilDepthFailOp = QSGD3D12PipelineState::StencilKeep; |
| sps.stencilPassOp = QSGD3D12PipelineState::StencilIncr; |
| |
| m_engine->queueSetStencilRef(stencilValue); |
| |
| int clipIndex = elementIndex; |
| while (m_renderList.at(--clipIndex).node != clip) { |
| Q_ASSERT(clipIndex >= 0); |
| } |
| const Element &ce = m_renderList.at(clipIndex); |
| Q_ASSERT(ce.node == clip); |
| |
| const QSGGeometry *g = clip->geometry(); |
| Q_ASSERT(g->attributeCount() == 1); |
| Q_ASSERT(g->attributes()[0].tupleSize == 2); |
| Q_ASSERT(g->attributes()[0].type == QSGGeometry::FloatType); |
| |
| setInputLayout(g, &sps); |
| m_engine->finalizePipeline(sps); |
| |
| Q_ASSERT(ce.cboSize > 0); |
| quint8 *p = m_cboData.data() + ce.cboOffset; |
| memcpy(p, m.constData(), 16 * sizeof(float)); |
| m_engine->markBufferDirty(m_constantBuf, ce.cboOffset, ce.cboSize); |
| |
| queueDrawCall(g, ce); |
| |
| ++stencilValue; |
| } |
| |
| void QSGD3D12Renderer::renderRenderNode(QSGRenderNode *node, int elementIndex) |
| { |
| QSGRenderNodePrivate *rd = QSGRenderNodePrivate::get(node); |
| RenderNodeState state; |
| |
| setupClipping(rd->m_clip_list, elementIndex); |
| |
| QMatrix4x4 pm = projectionMatrix(); |
| state.m_projectionMatrix = ± |
| state.m_scissorEnabled = m_currentClipTypes & ClipScissor; |
| state.m_stencilEnabled = m_currentClipTypes & ClipStencil; |
| state.m_scissorRect = m_activeScissorRect; |
| state.m_stencilValue = m_currentStencilValue; |
| |
| // ### rendernodes do not have the QSGBasicGeometryNode infrastructure |
| // for storing combined matrices, opacity and such, but perhaps they should. |
| QSGNode *xform = node->parent(); |
| QSGNode *root = rootNode(); |
| QMatrix4x4 modelview; |
| while (xform != root) { |
| if (xform->type() == QSGNode::TransformNodeType) { |
| modelview *= static_cast<QSGTransformNode *>(xform)->combinedMatrix(); |
| break; |
| } |
| xform = xform->parent(); |
| } |
| rd->m_matrix = &modelview; |
| |
| QSGNode *opacity = node->parent(); |
| rd->m_opacity = 1.0; |
| while (opacity != rootNode()) { |
| if (opacity->type() == QSGNode::OpacityNodeType) { |
| rd->m_opacity = static_cast<QSGOpacityNode *>(opacity)->combinedOpacity(); |
| break; |
| } |
| opacity = opacity->parent(); |
| } |
| |
| node->render(&state); |
| |
| m_engine->invalidateCachedFrameState(); |
| // For simplicity, reset viewport, scissor, blend factor, stencil ref when |
| // any of them got changed. This will likely be rare so skip these otherwise. |
| // Render target, pipeline state, draw call related stuff will be reset always. |
| const bool restoreMinimal = node->changedStates() == 0; |
| m_engine->restoreFrameState(restoreMinimal); |
| } |
| |
| QT_END_NAMESPACE |