| /**************************************************************************** |
| ** |
| ** Copyright (C) 2019 The Qt Company Ltd. |
| ** Copyright (C) 2016 Jolla Ltd, author: <gunnar.sletta@jollamobile.com> |
| ** Copyright (C) 2016 Robin Burchell <robin.burchell@viroteck.net> |
| ** 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 "qsgrhivisualizer_p.h" |
| #include <qmath.h> |
| #include <QQuickWindow> |
| #include <private/qsgmaterialrhishader_p.h> |
| #include <private/qsgshadersourcebuilder_p.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| namespace QSGBatchRenderer |
| { |
| |
| #define QSGNODE_TRAVERSE(NODE) for (QSGNode *child = NODE->firstChild(); child; child = child->nextSibling()) |
| #define SHADOWNODE_TRAVERSE(NODE) for (Node *child = NODE->firstChild(); child; child = child->sibling()) |
| #define QSGNODE_DIRTY_PARENT (QSGNode::DirtyNodeAdded \ |
| | QSGNode::DirtyOpacity \ |
| | QSGNode::DirtyMatrix \ |
| | QSGNode::DirtyNodeRemoved) |
| |
| QMatrix4x4 qsg_matrixForRoot(Node *node); |
| QRhiVertexInputAttribute::Format qsg_vertexInputFormat(const QSGGeometry::Attribute &a); |
| QRhiCommandBuffer::IndexFormat qsg_indexFormat(const QSGGeometry *geometry); |
| QRhiGraphicsPipeline::Topology qsg_topology(int geomDrawMode); |
| |
| RhiVisualizer::RhiVisualizer(Renderer *renderer) |
| : Visualizer(renderer) |
| { |
| } |
| |
| RhiVisualizer::~RhiVisualizer() |
| { |
| releaseResources(); |
| } |
| |
| void RhiVisualizer::releaseResources() |
| { |
| m_pipelines.releaseResources(); |
| |
| m_fade.releaseResources(); |
| |
| m_changeVis.releaseResources(); |
| m_batchVis.releaseResources(); |
| m_clipVis.releaseResources(); |
| m_overdrawVis.releaseResources(); |
| } |
| |
| void RhiVisualizer::prepareVisualize() |
| { |
| // Called before the render pass has begun (but after preparing the |
| // batches). Now is the time to put resource updates to the renderer's |
| // current m_resourceUpdates instance. |
| |
| if (m_visualizeMode == VisualizeNothing) |
| return; |
| |
| if (!m_vs.isValid()) { |
| m_vs = QSGMaterialRhiShaderPrivate::loadShader( |
| QLatin1String(":/qt-project.org/scenegraph/shaders_ng/visualization.vert.qsb")); |
| m_fs = QSGMaterialRhiShaderPrivate::loadShader( |
| QLatin1String(":/qt-project.org/scenegraph/shaders_ng/visualization.frag.qsb")); |
| } |
| |
| m_fade.prepare(this, m_renderer->m_rhi, m_renderer->m_resourceUpdates, m_renderer->renderPassDescriptor()); |
| |
| const bool forceUintIndex = m_renderer->m_uint32IndexForRhi; |
| |
| switch (m_visualizeMode) { |
| case VisualizeBatches: |
| m_batchVis.prepare(m_renderer->m_opaqueBatches, m_renderer->m_alphaBatches, |
| this, |
| m_renderer->m_rhi, m_renderer->m_resourceUpdates, |
| forceUintIndex); |
| break; |
| case VisualizeClipping: |
| m_clipVis.prepare(m_renderer->rootNode(), this, |
| m_renderer->m_rhi, m_renderer->m_resourceUpdates); |
| break; |
| case VisualizeChanges: |
| m_changeVis.prepare(m_renderer->m_nodes.value(m_renderer->rootNode()), |
| this, |
| m_renderer->m_rhi, m_renderer->m_resourceUpdates); |
| m_visualizeChangeSet.clear(); |
| break; |
| case VisualizeOverdraw: |
| m_overdrawVis.prepare(m_renderer->m_nodes.value(m_renderer->rootNode()), |
| this, |
| m_renderer->m_rhi, m_renderer->m_resourceUpdates); |
| break; |
| default: |
| Q_UNREACHABLE(); |
| break; |
| } |
| } |
| |
| void RhiVisualizer::visualize() |
| { |
| if (m_visualizeMode == VisualizeNothing) |
| return; |
| |
| QRhiCommandBuffer *cb = m_renderer->commandBuffer(); |
| m_fade.render(cb); |
| |
| switch (m_visualizeMode) { |
| case VisualizeBatches: |
| m_batchVis.render(cb); |
| break; |
| case VisualizeClipping: |
| m_clipVis.render(cb); |
| break; |
| case VisualizeChanges: |
| m_changeVis.render(cb); |
| break; |
| case VisualizeOverdraw: |
| m_overdrawVis.render(cb); |
| break; |
| default: |
| Q_UNREACHABLE(); |
| break; |
| } |
| } |
| |
| void RhiVisualizer::recordDrawCalls(const QVector<DrawCall> &drawCalls, |
| QRhiCommandBuffer *cb, |
| QRhiShaderResourceBindings *srb, |
| bool blendOneOne) |
| { |
| for (const DrawCall &dc : drawCalls) { |
| QRhiGraphicsPipeline *ps = m_pipelines.pipeline(this, m_renderer->m_rhi, srb, m_renderer->renderPassDescriptor(), |
| dc.vertex.topology, dc.vertex.format, dc.vertex.stride, |
| blendOneOne); |
| if (!ps) |
| continue; |
| cb->setGraphicsPipeline(ps); // no-op if same as the last one |
| QRhiCommandBuffer::DynamicOffset dynofs(0, dc.buf.ubufOffset); |
| cb->setShaderResources(srb, 1, &dynofs); |
| QRhiCommandBuffer::VertexInput vb(dc.buf.vbuf, dc.buf.vbufOffset); |
| if (dc.index.count) { |
| cb->setVertexInput(0, 1, &vb, dc.buf.ibuf, dc.buf.ibufOffset, dc.index.format); |
| cb->drawIndexed(dc.index.count); |
| } else { |
| cb->setVertexInput(0, 1, &vb); |
| cb->draw(dc.vertex.count); |
| } |
| } |
| } |
| |
| const QRhiShaderResourceBinding::StageFlags ubufVisibility = |
| QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage; |
| |
| void RhiVisualizer::Fade::prepare(RhiVisualizer *visualizer, |
| QRhi *rhi, QRhiResourceUpdateBatch *u, QRhiRenderPassDescriptor *rpDesc) |
| { |
| this->visualizer = visualizer; |
| |
| if (!vbuf) { |
| float v[] = { -1, 1, 1, 1, -1, -1, 1, -1 }; |
| vbuf = rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(v)); |
| if (!vbuf->build()) |
| return; |
| u->uploadStaticBuffer(vbuf, v); |
| } |
| |
| if (!ubuf) { |
| ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, DrawCall::UBUF_SIZE); |
| if (!ubuf->build()) |
| return; |
| float bgOpacity = 0.8f; |
| if (visualizer->m_visualizeMode == Visualizer::VisualizeBatches) |
| bgOpacity = 1.0; |
| QMatrix4x4 ident; |
| u->updateDynamicBuffer(ubuf, 0, 64, ident.constData()); // matrix |
| u->updateDynamicBuffer(ubuf, 64, 64, ident.constData()); // rotation |
| float color[4] = { 0.0f, 0.0f, 0.0f, bgOpacity }; |
| u->updateDynamicBuffer(ubuf, 128, 16, color); |
| float pattern = 0.0f; |
| u->updateDynamicBuffer(ubuf, 144, 4, &pattern); |
| qint32 projection = 0; |
| u->updateDynamicBuffer(ubuf, 148, 4, &projection); |
| } |
| |
| if (!srb) { |
| srb = rhi->newShaderResourceBindings(); |
| srb->setBindings({ QRhiShaderResourceBinding::uniformBuffer(0, ubufVisibility, ubuf) }); |
| if (!srb->build()) |
| return; |
| } |
| |
| if (!ps) { |
| ps = rhi->newGraphicsPipeline(); |
| ps->setTopology(QRhiGraphicsPipeline::TriangleStrip); |
| QRhiGraphicsPipeline::TargetBlend blend; // defaults to premul alpha, just what we need |
| blend.enable = true; |
| ps->setTargetBlends({ blend }); |
| ps->setShaderStages({ { QRhiShaderStage::Vertex, visualizer->m_vs }, |
| { QRhiShaderStage::Fragment, visualizer->m_fs } }); |
| QRhiVertexInputLayout inputLayout; |
| inputLayout.setBindings({ { 2 * sizeof(float) } }); |
| inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } }); |
| ps->setVertexInputLayout(inputLayout); |
| ps->setShaderResourceBindings(srb); |
| ps->setRenderPassDescriptor(rpDesc); |
| if (!ps->build()) |
| return; |
| } |
| } |
| |
| void RhiVisualizer::Fade::releaseResources() |
| { |
| delete ps; |
| ps = nullptr; |
| |
| delete srb; |
| srb = nullptr; |
| |
| delete ubuf; |
| ubuf = nullptr; |
| |
| delete vbuf; |
| vbuf = nullptr; |
| } |
| |
| void RhiVisualizer::Fade::render(QRhiCommandBuffer *cb) |
| { |
| cb->setGraphicsPipeline(ps); |
| cb->setViewport(visualizer->m_renderer->m_pstate.viewport); |
| cb->setShaderResources(); |
| QRhiCommandBuffer::VertexInput vb(vbuf, 0); |
| cb->setVertexInput(0, 1, &vb); |
| cb->draw(4); |
| } |
| |
| static void fillVertexIndex(RhiVisualizer::DrawCall *dc, QSGGeometry *g, bool withData, bool forceUintIndex) |
| { |
| dc->vertex.topology = qsg_topology(g->drawingMode()); |
| dc->vertex.format = qsg_vertexInputFormat(g->attributes()[0]); |
| dc->vertex.count = g->vertexCount(); |
| dc->vertex.stride = g->sizeOfVertex(); |
| if (withData) |
| dc->vertex.data = g->vertexData(); |
| |
| dc->index.format = forceUintIndex ? QRhiCommandBuffer::IndexUInt32 : qsg_indexFormat(g); |
| dc->index.count = g->indexCount(); |
| dc->index.stride = forceUintIndex ? sizeof(quint32) : g->sizeOfIndex(); |
| if (withData && g->indexCount()) |
| dc->index.data = g->indexData(); |
| } |
| |
| static inline uint aligned(uint v, uint byteAlign) |
| { |
| return (v + byteAlign - 1) & ~(byteAlign - 1); |
| } |
| |
| static bool ensureBuffer(QRhi *rhi, QRhiBuffer **buf, QRhiBuffer::UsageFlags usage, int newSize) |
| { |
| if (!*buf) { |
| *buf = rhi->newBuffer(QRhiBuffer::Dynamic, usage, newSize); |
| if (!(*buf)->build()) |
| return false; |
| } else if ((*buf)->size() < newSize) { |
| (*buf)->setSize(newSize); |
| if (!(*buf)->build()) |
| return false; |
| } |
| return true; |
| } |
| |
| QRhiGraphicsPipeline *RhiVisualizer::PipelineCache::pipeline(RhiVisualizer *visualizer, |
| QRhi *rhi, |
| QRhiShaderResourceBindings *srb, |
| QRhiRenderPassDescriptor *rpDesc, |
| QRhiGraphicsPipeline::Topology topology, |
| QRhiVertexInputAttribute::Format vertexFormat, |
| quint32 vertexStride, |
| bool blendOneOne) |
| { |
| for (int i = 0, ie = pipelines.count(); i != ie; ++i) { |
| const Pipeline &p(pipelines.at(i)); |
| if (p.topology == topology && p.format == vertexFormat && p.stride == vertexStride) |
| return p.ps; |
| } |
| |
| QRhiGraphicsPipeline *ps = rhi->newGraphicsPipeline(); |
| ps->setTopology(topology); |
| QRhiGraphicsPipeline::TargetBlend blend; // premul alpha |
| blend.enable = true; |
| if (blendOneOne) { |
| // only for visualizing overdraw, other modes use premul alpha |
| blend.srcColor = QRhiGraphicsPipeline::One; |
| blend.dstColor = QRhiGraphicsPipeline::One; |
| blend.srcAlpha = QRhiGraphicsPipeline::One; |
| blend.dstAlpha = QRhiGraphicsPipeline::One; |
| } |
| ps->setTargetBlends({ blend }); |
| ps->setShaderStages({ { QRhiShaderStage::Vertex, visualizer->m_vs }, |
| { QRhiShaderStage::Fragment, visualizer->m_fs } }); |
| QRhiVertexInputLayout inputLayout; |
| inputLayout.setBindings({ { vertexStride } }); |
| inputLayout.setAttributes({ { 0, 0, vertexFormat, 0 } }); |
| ps->setVertexInputLayout(inputLayout); |
| ps->setShaderResourceBindings(srb); |
| ps->setRenderPassDescriptor(rpDesc); |
| if (!ps->build()) |
| return nullptr; |
| |
| Pipeline p; |
| p.topology = topology; |
| p.format = vertexFormat; |
| p.stride = vertexStride; |
| p.ps = ps; |
| pipelines.append(p); |
| |
| return ps; |
| } |
| |
| void RhiVisualizer::PipelineCache::releaseResources() |
| { |
| for (int i = 0, ie = pipelines.count(); i != ie; ++i) |
| delete pipelines.at(i).ps; |
| |
| pipelines.clear(); |
| } |
| |
| void RhiVisualizer::ChangeVis::gather(Node *n) |
| { |
| if (n->type() == QSGNode::GeometryNodeType && n->element()->batch && visualizer->m_visualizeChangeSet.contains(n)) { |
| const uint dirty = visualizer->m_visualizeChangeSet.value(n); |
| const bool tinted = (dirty & QSGNODE_DIRTY_PARENT) != 0; |
| const QColor color = QColor::fromHsvF((rand() & 1023) / 1023.0f, 0.3f, 1.0f); |
| const float alpha = 0.5f; |
| |
| QMatrix4x4 matrix = visualizer->m_renderer->m_current_projection_matrix; |
| if (n->element()->batch->root) |
| matrix = matrix * qsg_matrixForRoot(n->element()->batch->root); |
| |
| QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(n->sgNode); |
| matrix = matrix * *gn->matrix(); |
| |
| QSGGeometry *g = gn->geometry(); |
| if (g->attributeCount() >= 1) { |
| DrawCall dc; |
| memcpy(dc.uniforms.data, matrix.constData(), 64); |
| QMatrix4x4 rotation; |
| memcpy(dc.uniforms.data + 64, rotation.constData(), 64); |
| float c[4] = { |
| float(color.redF()) * alpha, |
| float(color.greenF()) * alpha, |
| float(color.blueF()) * alpha, |
| alpha |
| }; |
| memcpy(dc.uniforms.data + 128, c, 16); |
| float pattern = tinted ? 0.5f : 0.0f; |
| memcpy(dc.uniforms.data + 144, &pattern, 4); |
| qint32 projection = 0; |
| memcpy(dc.uniforms.data + 148, &projection, 4); |
| |
| fillVertexIndex(&dc, g, true, false); |
| drawCalls.append(dc); |
| } |
| |
| // This is because many changes don't propegate their dirty state to the |
| // parent so the node updater will not unset these states. They are |
| // not used for anything so, unsetting it should have no side effects. |
| n->dirtyState = { }; |
| } |
| |
| SHADOWNODE_TRAVERSE(n) { |
| gather(child); |
| } |
| } |
| |
| void RhiVisualizer::ChangeVis::prepare(Node *n, RhiVisualizer *visualizer, |
| QRhi *rhi, QRhiResourceUpdateBatch *u) |
| { |
| this->visualizer = visualizer; |
| |
| drawCalls.clear(); |
| gather(n); |
| |
| if (drawCalls.isEmpty()) |
| return; |
| |
| const int ubufAlign = rhi->ubufAlignment(); |
| int vbufOffset = 0; |
| int ibufOffset = 0; |
| int ubufOffset = 0; |
| for (RhiVisualizer::DrawCall &dc : drawCalls) { |
| dc.buf.vbufOffset = aligned(vbufOffset, 4); |
| vbufOffset = dc.buf.vbufOffset + dc.vertex.count * dc.vertex.stride; |
| |
| dc.buf.ibufOffset = aligned(ibufOffset, 4); |
| ibufOffset = dc.buf.ibufOffset + dc.index.count * dc.index.stride; |
| |
| dc.buf.ubufOffset = aligned(ubufOffset, ubufAlign); |
| ubufOffset = dc.buf.ubufOffset + DrawCall::UBUF_SIZE; |
| } |
| |
| ensureBuffer(rhi, &vbuf, QRhiBuffer::VertexBuffer, vbufOffset); |
| if (ibufOffset) |
| ensureBuffer(rhi, &ibuf, QRhiBuffer::IndexBuffer, ibufOffset); |
| const int ubufSize = ubufOffset; |
| ensureBuffer(rhi, &ubuf, QRhiBuffer::UniformBuffer, ubufSize); |
| |
| for (RhiVisualizer::DrawCall &dc : drawCalls) { |
| u->updateDynamicBuffer(vbuf, dc.buf.vbufOffset, dc.vertex.count * dc.vertex.stride, dc.vertex.data); |
| dc.buf.vbuf = vbuf; |
| if (dc.index.count) { |
| u->updateDynamicBuffer(ibuf, dc.buf.ibufOffset, dc.index.count * dc.index.stride, dc.index.data); |
| dc.buf.ibuf = ibuf; |
| } |
| u->updateDynamicBuffer(ubuf, dc.buf.ubufOffset, DrawCall::UBUF_SIZE, dc.uniforms.data); |
| } |
| |
| if (!srb) { |
| srb = rhi->newShaderResourceBindings(); |
| srb->setBindings({ QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(0, ubufVisibility, ubuf, DrawCall::UBUF_SIZE) }); |
| if (!srb->build()) |
| return; |
| } |
| } |
| |
| void RhiVisualizer::ChangeVis::releaseResources() |
| { |
| delete srb; |
| srb = nullptr; |
| |
| delete ubuf; |
| ubuf = nullptr; |
| |
| delete ibuf; |
| ibuf = nullptr; |
| |
| delete vbuf; |
| vbuf = nullptr; |
| } |
| |
| void RhiVisualizer::ChangeVis::render(QRhiCommandBuffer *cb) |
| { |
| visualizer->recordDrawCalls(drawCalls, cb, srb); |
| } |
| |
| void RhiVisualizer::BatchVis::gather(Batch *b) |
| { |
| if (b->positionAttribute != 0) |
| return; |
| |
| QMatrix4x4 matrix(visualizer->m_renderer->m_current_projection_matrix); |
| if (b->root) |
| matrix = matrix * qsg_matrixForRoot(b->root); |
| |
| DrawCall dc; |
| |
| QMatrix4x4 rotation; |
| memcpy(dc.uniforms.data + 64, rotation.constData(), 64); |
| |
| QColor color = QColor::fromHsvF((rand() & 1023) / 1023.0, 1.0, 1.0); |
| |
| float c[4] = { |
| float(color.redF()), |
| float(color.greenF()), |
| float(color.blueF()), |
| 1.0f |
| }; |
| memcpy(dc.uniforms.data + 128, c, 16); |
| |
| float pattern = b->merged ? 0.0f : 1.0f; |
| memcpy(dc.uniforms.data + 144, &pattern, 4); |
| |
| qint32 projection = 0; |
| memcpy(dc.uniforms.data + 148, &projection, 4); |
| |
| if (b->merged) { |
| memcpy(dc.uniforms.data, matrix.constData(), 64); |
| |
| QSGGeometryNode *gn = b->first->node; |
| QSGGeometry *g = gn->geometry(); |
| |
| fillVertexIndex(&dc, g, false, forceUintIndex); |
| |
| for (int ds = 0; ds < b->drawSets.size(); ++ds) { |
| const DrawSet &set = b->drawSets.at(ds); |
| dc.buf.vbuf = b->vbo.buf; |
| dc.buf.vbufOffset = set.vertices; |
| dc.buf.ibuf = b->ibo.buf; |
| dc.buf.ibufOffset = set.indices; |
| dc.index.count = set.indexCount; |
| drawCalls.append(dc); |
| } |
| } else { |
| Element *e = b->first; |
| int vOffset = 0; |
| int iOffset = 0; |
| |
| while (e) { |
| QSGGeometryNode *gn = e->node; |
| QSGGeometry *g = gn->geometry(); |
| |
| QMatrix4x4 m = matrix * *gn->matrix(); |
| memcpy(dc.uniforms.data, m.constData(), 64); |
| |
| fillVertexIndex(&dc, g, false, forceUintIndex); |
| |
| dc.buf.vbuf = b->vbo.buf; |
| dc.buf.vbufOffset = vOffset; |
| if (g->indexCount()) { |
| dc.buf.ibuf = b->ibo.buf; |
| dc.buf.ibufOffset = iOffset; |
| } |
| |
| drawCalls.append(dc); |
| |
| vOffset += dc.vertex.count * dc.vertex.stride; |
| iOffset += dc.index.count * dc.index.stride; |
| |
| e = e->nextInBatch; |
| } |
| } |
| } |
| |
| void RhiVisualizer::BatchVis::prepare(const QDataBuffer<Batch *> &opaqueBatches, const QDataBuffer<Batch *> &alphaBatches, |
| RhiVisualizer *visualizer, |
| QRhi *rhi, QRhiResourceUpdateBatch *u, |
| bool forceUintIndex) |
| { |
| this->visualizer = visualizer; |
| this->forceUintIndex = forceUintIndex; |
| |
| drawCalls.clear(); |
| |
| srand(0); // To force random colors to be roughly the same every time.. |
| for (int i = 0; i < opaqueBatches.size(); ++i) |
| gather(opaqueBatches.at(i)); |
| for (int i = 0; i < alphaBatches.size(); ++i) |
| gather(alphaBatches.at(i)); |
| |
| if (drawCalls.isEmpty()) |
| return; |
| |
| const int ubufAlign = rhi->ubufAlignment(); |
| int ubufOffset = 0; |
| for (RhiVisualizer::DrawCall &dc : drawCalls) { |
| dc.buf.ubufOffset = aligned(ubufOffset, ubufAlign); |
| ubufOffset = dc.buf.ubufOffset + DrawCall::UBUF_SIZE; |
| } |
| |
| const int ubufSize = ubufOffset; |
| ensureBuffer(rhi, &ubuf, QRhiBuffer::UniformBuffer, ubufSize); |
| |
| for (RhiVisualizer::DrawCall &dc : drawCalls) |
| u->updateDynamicBuffer(ubuf, dc.buf.ubufOffset, DrawCall::UBUF_SIZE, dc.uniforms.data); |
| |
| if (!srb) { |
| srb = rhi->newShaderResourceBindings(); |
| srb->setBindings({ QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(0, ubufVisibility, ubuf, DrawCall::UBUF_SIZE) }); |
| if (!srb->build()) |
| return; |
| } |
| } |
| |
| void RhiVisualizer::BatchVis::releaseResources() |
| { |
| delete srb; |
| srb = nullptr; |
| |
| delete ubuf; |
| ubuf = nullptr; |
| } |
| |
| void RhiVisualizer::BatchVis::render(QRhiCommandBuffer *cb) |
| { |
| visualizer->recordDrawCalls(drawCalls, cb, srb); |
| } |
| |
| void RhiVisualizer::ClipVis::gather(QSGNode *node) |
| { |
| if (node->type() == QSGNode::ClipNodeType) { |
| QSGClipNode *clipNode = static_cast<QSGClipNode *>(node); |
| QMatrix4x4 matrix = visualizer->m_renderer->m_current_projection_matrix; |
| if (clipNode->matrix()) |
| matrix = matrix * *clipNode->matrix(); |
| |
| QSGGeometry *g = clipNode->geometry(); |
| if (g->attributeCount() >= 1) { |
| DrawCall dc; |
| memcpy(dc.uniforms.data, matrix.constData(), 64); |
| QMatrix4x4 rotation; |
| memcpy(dc.uniforms.data + 64, rotation.constData(), 64); |
| float c[4] = { 0.2f, 0.0f, 0.0f, 0.2f }; |
| memcpy(dc.uniforms.data + 128, c, 16); |
| float pattern = 0.5f; |
| memcpy(dc.uniforms.data + 144, &pattern, 4); |
| qint32 projection = 0; |
| memcpy(dc.uniforms.data + 148, &projection, 4); |
| fillVertexIndex(&dc, g, true, false); |
| drawCalls.append(dc); |
| } |
| } |
| |
| QSGNODE_TRAVERSE(node) { |
| gather(child); |
| } |
| } |
| |
| void RhiVisualizer::ClipVis::prepare(QSGNode *node, RhiVisualizer *visualizer, |
| QRhi *rhi, QRhiResourceUpdateBatch *u) |
| { |
| this->visualizer = visualizer; |
| |
| drawCalls.clear(); |
| gather(node); |
| |
| if (drawCalls.isEmpty()) |
| return; |
| |
| const int ubufAlign = rhi->ubufAlignment(); |
| int vbufOffset = 0; |
| int ibufOffset = 0; |
| int ubufOffset = 0; |
| for (RhiVisualizer::DrawCall &dc : drawCalls) { |
| dc.buf.vbufOffset = aligned(vbufOffset, 4); |
| vbufOffset = dc.buf.vbufOffset + dc.vertex.count * dc.vertex.stride; |
| |
| dc.buf.ibufOffset = aligned(ibufOffset, 4); |
| ibufOffset = dc.buf.ibufOffset + dc.index.count * dc.index.stride; |
| |
| dc.buf.ubufOffset = aligned(ubufOffset, ubufAlign); |
| ubufOffset = dc.buf.ubufOffset + DrawCall::UBUF_SIZE; |
| } |
| |
| ensureBuffer(rhi, &vbuf, QRhiBuffer::VertexBuffer, vbufOffset); |
| if (ibufOffset) |
| ensureBuffer(rhi, &ibuf, QRhiBuffer::IndexBuffer, ibufOffset); |
| const int ubufSize = ubufOffset; |
| ensureBuffer(rhi, &ubuf, QRhiBuffer::UniformBuffer, ubufSize); |
| |
| for (RhiVisualizer::DrawCall &dc : drawCalls) { |
| u->updateDynamicBuffer(vbuf, dc.buf.vbufOffset, dc.vertex.count * dc.vertex.stride, dc.vertex.data); |
| dc.buf.vbuf = vbuf; |
| if (dc.index.count) { |
| u->updateDynamicBuffer(ibuf, dc.buf.ibufOffset, dc.index.count * dc.index.stride, dc.index.data); |
| dc.buf.ibuf = ibuf; |
| } |
| u->updateDynamicBuffer(ubuf, dc.buf.ubufOffset, DrawCall::UBUF_SIZE, dc.uniforms.data); |
| } |
| |
| if (!srb) { |
| srb = rhi->newShaderResourceBindings(); |
| srb->setBindings({ QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(0, ubufVisibility, ubuf, DrawCall::UBUF_SIZE) }); |
| if (!srb->build()) |
| return; |
| } |
| } |
| |
| void RhiVisualizer::ClipVis::releaseResources() |
| { |
| delete srb; |
| srb = nullptr; |
| |
| delete ubuf; |
| ubuf = nullptr; |
| |
| delete ibuf; |
| ibuf = nullptr; |
| |
| delete vbuf; |
| vbuf = nullptr; |
| } |
| |
| void RhiVisualizer::ClipVis::render(QRhiCommandBuffer *cb) |
| { |
| visualizer->recordDrawCalls(drawCalls, cb, srb); |
| } |
| |
| void RhiVisualizer::OverdrawVis::gather(Node *n) |
| { |
| if (n->type() == QSGNode::GeometryNodeType && n->element()->batch) { |
| QMatrix4x4 matrix = visualizer->m_renderer->m_current_projection_matrix; |
| matrix(2, 2) = visualizer->m_renderer->m_zRange; |
| matrix(2, 3) = 1.0f - n->element()->order * visualizer->m_renderer->m_zRange; |
| |
| if (n->element()->batch->root) |
| matrix = matrix * qsg_matrixForRoot(n->element()->batch->root); |
| |
| QSGGeometryNode *gn = static_cast<QSGGeometryNode *>(n->sgNode); |
| matrix = matrix * *gn->matrix(); |
| |
| QSGGeometry *g = gn->geometry(); |
| if (g->attributeCount() >= 1) { |
| DrawCall dc; |
| memcpy(dc.uniforms.data, matrix.constData(), 64); |
| memcpy(dc.uniforms.data + 64, rotation.constData(), 64); |
| |
| float c[4]; |
| const float ca = 0.33f; |
| if (n->element()->batch->isOpaque) { |
| c[0] = ca * 0.3f; c[1] = ca * 1.0f; c[2] = ca * 0.3f; c[3] = ca; |
| } else { |
| c[0] = ca * 1.0f; c[1] = ca * 0.3f; c[2] = ca * 0.3f; c[3] = ca; |
| } |
| memcpy(dc.uniforms.data + 128, c, 16); |
| float pattern = 0.0f; |
| memcpy(dc.uniforms.data + 144, &pattern, 4); |
| qint32 projection = 1; |
| memcpy(dc.uniforms.data + 148, &projection, 4); |
| |
| fillVertexIndex(&dc, g, true, false); |
| drawCalls.append(dc); |
| } |
| } |
| |
| SHADOWNODE_TRAVERSE(n) { |
| gather(child); |
| } |
| } |
| |
| void RhiVisualizer::OverdrawVis::prepare(Node *n, RhiVisualizer *visualizer, |
| QRhi *rhi, QRhiResourceUpdateBatch *u) |
| { |
| this->visualizer = visualizer; |
| |
| step += float(M_PI * 2 / 1000.0); |
| if (step > float(M_PI * 2)) |
| step = 0.0f; |
| |
| const float yfix = rhi->isYUpInNDC() ? 1.0f : -1.0f; |
| rotation.setToIdentity(); |
| rotation.translate(0.0f, 0.5f * yfix, 4.0f); |
| rotation.scale(2.0f, 2.0f, 1.0f); |
| rotation.rotate(-30.0f * yfix, 1.0f, 0.0f, 0.0f); |
| rotation.rotate(80.0f * std::sin(step), 0.0f, 1.0f, 0.0f); |
| rotation.translate(0.0f, 0.0f, -1.0f); |
| |
| drawCalls.clear(); |
| gather(n); |
| |
| if (!box.vbuf) { |
| const float v[] = { |
| // lower |
| -1, 1, 0, 1, 1, 0, |
| -1, 1, 0, -1, -1, 0, |
| 1, 1, 0, 1, -1, 0, |
| -1, -1, 0, 1, -1, 0, |
| |
| // upper |
| -1, 1, 1, 1, 1, 1, |
| -1, 1, 1, -1, -1, 1, |
| 1, 1, 1, 1, -1, 1, |
| -1, -1, 1, 1, -1, 1, |
| |
| // sides |
| -1, -1, 0, -1, -1, 1, |
| 1, -1, 0, 1, -1, 1, |
| -1, 1, 0, -1, 1, 1, |
| 1, 1, 0, 1, 1, 1 |
| }; |
| box.vbuf = rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(v)); |
| if (!box.vbuf->build()) |
| return; |
| u->uploadStaticBuffer(box.vbuf, v); |
| } |
| |
| if (!box.ubuf) { |
| box.ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, DrawCall::UBUF_SIZE); |
| if (!box.ubuf->build()) |
| return; |
| QMatrix4x4 ident; |
| u->updateDynamicBuffer(box.ubuf, 0, 64, ident.constData()); |
| float color[4] = { 0.5f, 0.5f, 1.0f, 1.0f }; |
| u->updateDynamicBuffer(box.ubuf, 128, 16, color); |
| float pattern = 0.0f; |
| u->updateDynamicBuffer(box.ubuf, 144, 4, &pattern); |
| qint32 projection = 1; |
| u->updateDynamicBuffer(box.ubuf, 148, 4, &projection); |
| } |
| |
| u->updateDynamicBuffer(box.ubuf, 64, 64, rotation.constData()); |
| |
| if (!box.srb) { |
| box.srb = rhi->newShaderResourceBindings(); |
| box.srb->setBindings({ QRhiShaderResourceBinding::uniformBuffer(0, ubufVisibility, box.ubuf) }); |
| if (!box.srb->build()) |
| return; |
| } |
| |
| if (!box.ps) { |
| box.ps = rhi->newGraphicsPipeline(); |
| box.ps->setTopology(QRhiGraphicsPipeline::Lines); |
| box.ps->setLineWidth(2); // may be be ignored (D3D, Metal), but may be used on GL and Vulkan |
| QRhiGraphicsPipeline::TargetBlend blend; |
| blend.enable = true; |
| blend.srcColor = QRhiGraphicsPipeline::One; |
| blend.dstColor = QRhiGraphicsPipeline::One; |
| blend.srcAlpha = QRhiGraphicsPipeline::One; |
| blend.dstAlpha = QRhiGraphicsPipeline::One; |
| box.ps->setTargetBlends({ blend }); |
| box.ps->setShaderStages({ { QRhiShaderStage::Vertex, visualizer->m_vs }, |
| { QRhiShaderStage::Fragment, visualizer->m_fs } }); |
| QRhiVertexInputLayout inputLayout; |
| inputLayout.setBindings({ { 3 * sizeof(float) } }); |
| inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float3, 0 } }); |
| box.ps->setVertexInputLayout(inputLayout); |
| box.ps->setShaderResourceBindings(box.srb); |
| box.ps->setRenderPassDescriptor(visualizer->m_renderer->renderPassDescriptor()); |
| if (!box.ps->build()) |
| return; |
| } |
| |
| if (drawCalls.isEmpty()) |
| return; |
| |
| const int ubufAlign = rhi->ubufAlignment(); |
| int vbufOffset = 0; |
| int ibufOffset = 0; |
| int ubufOffset = 0; |
| for (RhiVisualizer::DrawCall &dc : drawCalls) { |
| dc.buf.vbufOffset = aligned(vbufOffset, 4); |
| vbufOffset = dc.buf.vbufOffset + dc.vertex.count * dc.vertex.stride; |
| |
| dc.buf.ibufOffset = aligned(ibufOffset, 4); |
| ibufOffset = dc.buf.ibufOffset + dc.index.count * dc.index.stride; |
| |
| dc.buf.ubufOffset = aligned(ubufOffset, ubufAlign); |
| ubufOffset = dc.buf.ubufOffset + DrawCall::UBUF_SIZE; |
| } |
| |
| ensureBuffer(rhi, &vbuf, QRhiBuffer::VertexBuffer, vbufOffset); |
| if (ibufOffset) |
| ensureBuffer(rhi, &ibuf, QRhiBuffer::IndexBuffer, ibufOffset); |
| const int ubufSize = ubufOffset; |
| ensureBuffer(rhi, &ubuf, QRhiBuffer::UniformBuffer, ubufSize); |
| |
| for (RhiVisualizer::DrawCall &dc : drawCalls) { |
| u->updateDynamicBuffer(vbuf, dc.buf.vbufOffset, dc.vertex.count * dc.vertex.stride, dc.vertex.data); |
| dc.buf.vbuf = vbuf; |
| if (dc.index.count) { |
| u->updateDynamicBuffer(ibuf, dc.buf.ibufOffset, dc.index.count * dc.index.stride, dc.index.data); |
| dc.buf.ibuf = ibuf; |
| } |
| u->updateDynamicBuffer(ubuf, dc.buf.ubufOffset, DrawCall::UBUF_SIZE, dc.uniforms.data); |
| } |
| |
| if (!srb) { |
| srb = rhi->newShaderResourceBindings(); |
| srb->setBindings({ QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(0, ubufVisibility, ubuf, DrawCall::UBUF_SIZE) }); |
| if (!srb->build()) |
| return; |
| } |
| } |
| |
| void RhiVisualizer::OverdrawVis::releaseResources() |
| { |
| delete srb; |
| srb = nullptr; |
| |
| delete ubuf; |
| ubuf = nullptr; |
| |
| delete ibuf; |
| ibuf = nullptr; |
| |
| delete vbuf; |
| vbuf = nullptr; |
| |
| delete box.ps; |
| box.ps = nullptr; |
| |
| delete box.srb; |
| box.srb = nullptr; |
| |
| delete box.ubuf; |
| box.ubuf = nullptr; |
| |
| delete box.vbuf; |
| box.vbuf = nullptr; |
| } |
| |
| void RhiVisualizer::OverdrawVis::render(QRhiCommandBuffer *cb) |
| { |
| cb->setGraphicsPipeline(box.ps); |
| cb->setShaderResources(); |
| QRhiCommandBuffer::VertexInput vb(box.vbuf, 0); |
| cb->setVertexInput(0, 1, &vb); |
| cb->draw(24); |
| |
| visualizer->recordDrawCalls(drawCalls, cb, srb, true); |
| } |
| |
| } |
| |
| QT_END_NAMESPACE |