// 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>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "qsgrhivisualizer_p.h"
#include <qmath.h>
#include <private/qsgmaterialshader_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, QRhi *rhi);

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 = QSGMaterialShaderPrivate::loadShader(
                    QLatin1String(":/qt-project.org/scenegraph/shaders_ng/visualization.vert.qsb"));
        m_fs = QSGMaterialShaderPrivate::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->renderTarget().rpDesc);

    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->renderTarget().cb;
    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->renderTarget().rpDesc,
                                                        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->create())
            return;
        u->uploadStaticBuffer(vbuf, v);
    }

    if (!ubuf) {
        ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, DrawCall::UBUF_SIZE);
        if (!ubuf->create())
            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->create())
            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->create())
            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, QRhi *rhi)
{
    dc->vertex.topology = qsg_topology(g->drawingMode(), rhi);
    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, quint32 newSize)
{
    if (!*buf) {
        *buf = rhi->newBuffer(QRhiBuffer::Dynamic, usage, newSize);
        if (!(*buf)->create())
            return false;
    } else if ((*buf)->size() < newSize) {
        (*buf)->setSize(newSize);
        if (!(*buf)->create())
            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.size(); 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->create())
        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.size(); 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((visualizer->m_randomGenerator.generate() & 1023) / 1023.0f, 0.3f, 1.0f).toRgb();
        const float alpha = 0.5f;

        QMatrix4x4 matrix = visualizer->m_renderer->m_current_projection_matrix[0];
        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, visualizer->m_renderer->m_rhi);
            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->create())
            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[0]);
    if (b->root)
        matrix = matrix * qsg_matrixForRoot(b->root);

    DrawCall dc;

    QMatrix4x4 rotation;
    memcpy(dc.uniforms.data + 64, rotation.constData(), 64);

    const QColor color = QColor::fromHsvF((visualizer->m_randomGenerator.generate() & 1023) / 1023.0, 1.0, 1.0).toRgb();

    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, visualizer->m_renderer->m_rhi);

        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, visualizer->m_renderer->m_rhi);

            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();

    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->create())
            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[0];
        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, visualizer->m_renderer->m_rhi);
            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->create())
            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[0];
        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, visualizer->m_renderer->m_rhi);
            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->create())
            return;
        u->uploadStaticBuffer(box.vbuf, v);
    }

    if (!box.ubuf) {
        box.ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, DrawCall::UBUF_SIZE);
        if (!box.ubuf->create())
            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->create())
            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->renderTarget().rpDesc);
        if (!box.ps->create())
            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->create())
            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
