File indexing completed on 2024-05-19 15:09:25

0001 /*
0002     This file is part of the KDE project
0003 
0004     SPDX-FileCopyrightText: 2014 Fredrik Höglund <fredrik@kde.org>
0005     SPDX-FileCopyrightText: 2014 Marco Martin <mart@kde.org>
0006     SPDX-FileCopyrightText: 2015 David Edmundson <davidedmundson@kde.org>
0007 
0008     SPDX-License-Identifier: LGPL-2.0-or-later
0009 */
0010 
0011 #include "plotter.h"
0012 
0013 #include <QOpenGLContext>
0014 #include <QOpenGLShaderProgram>
0015 
0016 #include <QPainterPath>
0017 #include <QPolygonF>
0018 
0019 #include <QMatrix4x4>
0020 #include <QVector2D>
0021 
0022 #include <QSGSimpleTextureNode>
0023 #include <QSGTexture>
0024 
0025 #include <QDebug>
0026 
0027 #include <math.h>
0028 
0029 // completely arbitrary
0030 static int s_defaultSampleSize = 40;
0031 
0032 PlotData::PlotData(QObject *parent)
0033     : QObject(parent)
0034     , m_min(std::numeric_limits<qreal>::max())
0035     , m_max(std::numeric_limits<qreal>::min())
0036     , m_sampleSize(s_defaultSampleSize)
0037 {
0038     m_values.reserve(s_defaultSampleSize);
0039     for (int i = 0; i < s_defaultSampleSize; ++i) {
0040         m_values << 0.0;
0041     }
0042 }
0043 
0044 void PlotData::setColor(const QColor &color)
0045 {
0046     if (m_color == color) {
0047         return;
0048     }
0049 
0050     m_color = color;
0051 
0052     Q_EMIT colorChanged();
0053 }
0054 
0055 QColor PlotData::color() const
0056 {
0057     return m_color;
0058 }
0059 
0060 qreal PlotData::max() const
0061 {
0062     return m_max;
0063 }
0064 
0065 qreal PlotData::min() const
0066 {
0067     return m_min;
0068 }
0069 
0070 void PlotData::setSampleSize(int size)
0071 {
0072     if (m_sampleSize == size) {
0073         return;
0074     }
0075 
0076     m_values.reserve(size);
0077     if (m_values.size() > size) {
0078         const int numberToRemove = (m_values.size() - size);
0079         for (int i = 0; i < numberToRemove; ++i) {
0080             m_values.removeFirst();
0081         }
0082     } else if (m_values.size() < size) {
0083         const int numberToAdd = (size - m_values.size());
0084         for (int i = 0; i < numberToAdd; ++i) {
0085             m_values.prepend(0.0);
0086         }
0087     }
0088 
0089     m_sampleSize = size;
0090 }
0091 
0092 QString PlotData::label() const
0093 {
0094     return m_label;
0095 }
0096 
0097 void PlotData::setLabel(const QString &label)
0098 {
0099     if (m_label == label) {
0100         return;
0101     }
0102 
0103     m_label = label;
0104     Q_EMIT labelChanged();
0105 }
0106 
0107 void PlotData::addSample(qreal value)
0108 {
0109     // assume at this point we'll have to pop a single time to stay in size
0110     if (m_values.size() >= m_sampleSize) {
0111         m_values.removeFirst();
0112     }
0113 
0114     m_values.push_back(value);
0115 
0116     m_max = std::numeric_limits<qreal>::min();
0117     m_min = std::numeric_limits<qreal>::max();
0118     for (auto v : std::as_const(m_values)) {
0119         if (v > m_max) {
0120             m_max = v;
0121         } else if (v < m_min) {
0122             m_min = v;
0123         }
0124     }
0125 
0126     Q_EMIT valuesChanged();
0127 }
0128 
0129 QList<qreal> PlotData::values() const
0130 {
0131     return m_values;
0132 }
0133 
0134 const char *vs_source =
0135     "attribute vec4 vertex;\n"
0136     "varying float gradient;\n"
0137 
0138     "uniform mat4 matrix;\n"
0139     "uniform float yMin;\n"
0140     "uniform float yMax;\n"
0141 
0142     "void main(void) {\n"
0143     "    gradient = (vertex.y - yMin) / (yMax - yMin);"
0144     "    gl_Position = matrix * vertex;\n"
0145     "}";
0146 
0147 const char *fs_source =
0148     "uniform vec4 color1;\n"
0149     "uniform vec4 color2;\n"
0150 
0151     "varying float gradient;\n"
0152 
0153     "void main(void) {\n"
0154     "    gl_FragColor = mix(color1, color2, gradient);\n"
0155     "}";
0156 
0157 // --------------------------------------------------
0158 
0159 class PlotTexture : public QSGTexture
0160 {
0161 public:
0162     PlotTexture(QOpenGLContext *ctx);
0163     ~PlotTexture() override;
0164 
0165     void bind() override final;
0166     bool hasAlphaChannel() const override final
0167     {
0168         return true;
0169     }
0170     bool hasMipmaps() const override final
0171     {
0172         return false;
0173     }
0174     int textureId() const override final
0175     {
0176         return m_texture;
0177     }
0178     QSize textureSize() const override final
0179     {
0180         return m_size;
0181     }
0182 
0183     void recreate(const QSize &size);
0184     GLuint fbo() const
0185     {
0186         return m_fbo;
0187     }
0188 
0189 private:
0190     GLuint m_texture = 0;
0191     GLuint m_fbo = 0;
0192     GLenum m_internalFormat;
0193     bool m_haveTexStorage;
0194     QSize m_size;
0195 };
0196 
0197 PlotTexture::PlotTexture(QOpenGLContext *ctx)
0198     : QSGTexture()
0199 {
0200     QPair<int, int> version = ctx->format().version();
0201 
0202     if (ctx->isOpenGLES()) {
0203         m_haveTexStorage = version >= qMakePair(3, 0) || ctx->hasExtension("GL_EXT_texture_storage");
0204         m_internalFormat = m_haveTexStorage ? GL_RGBA8 : GL_RGBA;
0205     } else {
0206         m_haveTexStorage = version >= qMakePair(4, 2) || ctx->hasExtension("GL_ARB_texture_storage");
0207         m_internalFormat = GL_RGBA8;
0208     }
0209 
0210     glGenFramebuffers(1, &m_fbo);
0211 }
0212 
0213 PlotTexture::~PlotTexture()
0214 {
0215     if (m_texture) {
0216         glDeleteTextures(1, &m_texture);
0217     }
0218 
0219     glDeleteFramebuffers(1, &m_fbo);
0220 }
0221 
0222 void PlotTexture::bind()
0223 {
0224     glBindTexture(GL_TEXTURE_2D, m_texture);
0225 }
0226 
0227 void PlotTexture::recreate(const QSize &size)
0228 {
0229     if (m_texture) {
0230         glDeleteTextures(1, &m_texture);
0231     }
0232 
0233     glGenTextures(1, &m_texture);
0234     glBindTexture(GL_TEXTURE_2D, m_texture);
0235     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
0236     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
0237     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
0238     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
0239     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
0240 
0241     if (m_haveTexStorage) {
0242         glTexStorage2D(GL_TEXTURE_2D, 1, m_internalFormat, size.width(), size.height());
0243     } else {
0244         glTexImage2D(GL_TEXTURE_2D, 0, m_internalFormat, size.width(), size.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
0245     }
0246 
0247     glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
0248     glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texture, 0);
0249 
0250     m_size = size;
0251 }
0252 
0253 class PlotSGNode : public QSGSimpleTextureNode
0254 {
0255 public:
0256     PlotSGNode();
0257     void bind()
0258     {
0259         m_program->bind();
0260     }
0261     void setMatrix(const QMatrix4x4 &matrix)
0262     {
0263         m_program->setUniformValue(u_matrix, matrix);
0264     }
0265     void setColor1(const QColor &color)
0266     {
0267         m_program->setUniformValue(u_color1, color);
0268     }
0269     void setColor2(const QColor &color)
0270     {
0271         m_program->setUniformValue(u_color2, color);
0272     }
0273     void setYMin(float min)
0274     {
0275         m_program->setUniformValue(u_yMin, min);
0276     }
0277     void setYMax(float max)
0278     {
0279         m_program->setUniformValue(u_yMax, max);
0280     }
0281     ~PlotSGNode() override = default;
0282 
0283 private:
0284     std::unique_ptr<QOpenGLShaderProgram> m_program;
0285     int u_matrix;
0286     int u_color1;
0287     int u_color2;
0288     int u_yMin;
0289     int u_yMax;
0290 };
0291 
0292 PlotSGNode::PlotSGNode()
0293     : m_program(std::make_unique<QOpenGLShaderProgram>())
0294 {
0295     setOwnsTexture(true);
0296     m_program->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vs_source);
0297     m_program->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fs_source);
0298     m_program->bindAttributeLocation("vertex", 0);
0299     m_program->link();
0300 
0301     u_yMin = m_program->uniformLocation("yMin");
0302     u_yMax = m_program->uniformLocation("yMax");
0303     u_color1 = m_program->uniformLocation("color1");
0304     u_color2 = m_program->uniformLocation("color2");
0305     u_matrix = m_program->uniformLocation("matrix");
0306 }
0307 
0308 // ----------------------
0309 
0310 Plotter::Plotter(QQuickItem *parent)
0311     : QQuickItem(parent)
0312     , m_min(0)
0313     , m_max(0)
0314     , m_rangeMax(0)
0315     , m_rangeMin(0)
0316     , m_sampleSize(s_defaultSampleSize)
0317     , m_horizontalLineCount(5)
0318     , m_stacked(true)
0319     , m_autoRange(true)
0320 {
0321     setFlag(ItemHasContents);
0322     connect(this, &Plotter::windowChanged, this, [this]() {
0323         if (m_window) {
0324             disconnect(m_window.data(), &QQuickWindow::beforeRendering, this, &Plotter::render);
0325         }
0326         m_window.clear();
0327         // when the window changes, the node gets deleted
0328         m_node = nullptr;
0329     });
0330 }
0331 
0332 Plotter::~Plotter()
0333 {
0334 }
0335 
0336 qreal Plotter::max() const
0337 {
0338     return m_max;
0339 }
0340 
0341 qreal Plotter::min() const
0342 {
0343     return m_min;
0344 }
0345 
0346 int Plotter::sampleSize() const
0347 {
0348     return m_sampleSize;
0349 }
0350 
0351 void Plotter::setSampleSize(int size)
0352 {
0353     if (m_sampleSize == size) {
0354         return;
0355     }
0356 
0357     m_sampleSize = size;
0358 
0359     m_mutex.lock();
0360     for (auto data : std::as_const(m_plotData)) {
0361         data->setSampleSize(size);
0362     }
0363     m_mutex.unlock();
0364 
0365     update();
0366     Q_EMIT sampleSizeChanged();
0367 }
0368 
0369 bool Plotter::isStacked() const
0370 {
0371     return m_stacked;
0372 }
0373 
0374 void Plotter::setStacked(bool stacked)
0375 {
0376     if (m_stacked == stacked) {
0377         return;
0378     }
0379 
0380     m_stacked = stacked;
0381 
0382     Q_EMIT stackedChanged();
0383     update();
0384 }
0385 
0386 bool Plotter::isAutoRange() const
0387 {
0388     return m_autoRange;
0389 }
0390 
0391 void Plotter::setAutoRange(bool autoRange)
0392 {
0393     if (m_autoRange == autoRange) {
0394         return;
0395     }
0396 
0397     m_autoRange = autoRange;
0398 
0399     Q_EMIT autoRangeChanged();
0400     normalizeData();
0401     update();
0402 }
0403 
0404 qreal Plotter::rangeMax() const
0405 {
0406     if (m_autoRange) {
0407         return m_max;
0408     } else {
0409         return m_rangeMax;
0410     }
0411 }
0412 
0413 void Plotter::setRangeMax(qreal max)
0414 {
0415     if (m_rangeMax == max) {
0416         return;
0417     }
0418 
0419     m_rangeMax = max;
0420 
0421     Q_EMIT rangeMaxChanged();
0422     normalizeData();
0423     update();
0424 }
0425 
0426 qreal Plotter::rangeMin() const
0427 {
0428     if (m_autoRange) {
0429         return m_min;
0430     } else {
0431         return m_rangeMin;
0432     }
0433 }
0434 
0435 void Plotter::setRangeMin(qreal min)
0436 {
0437     if (m_rangeMin == min) {
0438         return;
0439     }
0440 
0441     m_rangeMin = min;
0442 
0443     Q_EMIT rangeMinChanged();
0444     normalizeData();
0445     update();
0446 }
0447 
0448 void Plotter::setGridColor(const QColor &color)
0449 {
0450     if (m_gridColor == color) {
0451         return;
0452     }
0453 
0454     m_gridColor = color;
0455 
0456     Q_EMIT gridColorChanged();
0457 }
0458 
0459 QColor Plotter::gridColor() const
0460 {
0461     return m_gridColor;
0462 }
0463 
0464 int Plotter::horizontalGridLineCount()
0465 {
0466     return m_horizontalLineCount;
0467 }
0468 
0469 void Plotter::setHorizontalGridLineCount(int count)
0470 {
0471     if (m_horizontalLineCount == count) {
0472         return;
0473     }
0474 
0475     m_horizontalLineCount = count;
0476     Q_EMIT horizontalGridLineCountChanged();
0477 }
0478 
0479 void Plotter::addSample(qreal value)
0480 {
0481     if (m_plotData.count() != 1) {
0482         qWarning() << "Must add a new value per data set, pass an array of values instead";
0483         return;
0484     }
0485 
0486     addSample(QList<qreal>() << value);
0487 }
0488 
0489 void Plotter::addSample(const QList<qreal> &value)
0490 {
0491     if (value.count() != m_plotData.count()) {
0492         qWarning() << "Must add a new value per data set";
0493         return;
0494     }
0495 
0496     int i = 0;
0497     m_mutex.lock();
0498     for (auto data : std::as_const(m_plotData)) {
0499         data->addSample(value.value(i));
0500         ++i;
0501     }
0502     m_mutex.unlock();
0503 
0504     normalizeData();
0505 
0506     update();
0507 }
0508 
0509 void Plotter::dataSet_append(QQmlListProperty<PlotData> *list, PlotData *item)
0510 {
0511     // encase all m_plotData access in a mutex, since rendering is usually done in another thread
0512     Plotter *p = static_cast<Plotter *>(list->object);
0513     p->m_mutex.lock();
0514     p->m_plotData.append(item);
0515     p->m_mutex.unlock();
0516 }
0517 
0518 int Plotter::dataSet_count(QQmlListProperty<PlotData> *list)
0519 {
0520     Plotter *p = static_cast<Plotter *>(list->object);
0521     return p->m_plotData.count();
0522 }
0523 
0524 PlotData *Plotter::dataSet_at(QQmlListProperty<PlotData> *list, int index)
0525 {
0526     Plotter *p = static_cast<Plotter *>(list->object);
0527     p->m_mutex.lock();
0528     PlotData *d = p->m_plotData.at(index);
0529     p->m_mutex.unlock();
0530     return d;
0531 }
0532 
0533 void Plotter::dataSet_clear(QQmlListProperty<PlotData> *list)
0534 {
0535     Plotter *p = static_cast<Plotter *>(list->object);
0536 
0537     p->m_mutex.lock();
0538     p->m_plotData.clear();
0539     p->m_mutex.unlock();
0540 }
0541 
0542 QQmlListProperty<PlotData> Plotter::dataSets()
0543 {
0544     return QQmlListProperty<PlotData>(this, nullptr, Plotter::dataSet_append, Plotter::dataSet_count, Plotter::dataSet_at, Plotter::dataSet_clear);
0545 }
0546 
0547 // Catmull-Rom interpolation
0548 QPainterPath Plotter::interpolate(const QVector<qreal> &p, qreal x0, qreal x1) const
0549 {
0550     QPainterPath path;
0551 
0552     /* clang-format off */
0553     const QMatrix4x4 matrix( 0,    1,    0,     0,
0554                             -1/6., 1,    1/6.,  0,
0555                              0,    1/6., 1,    -1/6.,
0556                              0,    0,    1,     0);
0557     /* clang-format on */
0558 
0559     const qreal xDelta = (x1 - x0) / (p.count() - 3);
0560     qreal x = x0 - xDelta;
0561 
0562     path.moveTo(x0, p[0]);
0563 
0564     for (int i = 1; i < p.count() - 2; i++) {
0565         /* clang-format off */
0566         const QMatrix4x4 points(x,              p[i-1], 0, 0,
0567                                 x + xDelta * 1, p[i+0], 0, 0,
0568                                 x + xDelta * 2, p[i+1], 0, 0,
0569                                 x + xDelta * 3, p[i+2], 0, 0);
0570 
0571         const QMatrix4x4 res = matrix * points;
0572 
0573         path.cubicTo(res(1, 0), res(1, 1),
0574                      res(2, 0), res(2, 1),
0575                      res(3, 0), res(3, 1));
0576         /* clang-format on */
0577 
0578         x += xDelta;
0579     }
0580 
0581     return path;
0582 }
0583 
0584 void Plotter::render()
0585 {
0586     if (!window() || !m_node || !m_node->texture()) {
0587         return;
0588     }
0589 
0590     GLuint rb;
0591 
0592     if (m_haveMSAA && m_haveFramebufferBlit) {
0593         // Allocate a temporary MSAA renderbuffer
0594         glGenRenderbuffers(1, &rb);
0595         glBindRenderbuffer(GL_RENDERBUFFER, rb);
0596         glRenderbufferStorageMultisample(GL_RENDERBUFFER, m_samples, m_internalFormat, width(), height());
0597 
0598         // Attach it to the framebuffer object
0599         glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
0600         glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb);
0601     } else {
0602         // If we don't have MSAA support we render directly into the texture
0603         glBindFramebuffer(GL_FRAMEBUFFER, static_cast<PlotTexture *>(m_node->texture())->fbo());
0604     }
0605 
0606     glViewport(0, 0, width(), height());
0607 
0608     // Clear the color buffer
0609     glClearColor(0.0, 0.0, 0.0, 0.0);
0610     glClear(GL_COLOR_BUFFER_BIT);
0611 
0612     // Add horizontal lines
0613     qreal lineSpacing = height() / m_horizontalLineCount;
0614 
0615     QVector<QVector2D> vertices;
0616 
0617     // don't draw the bottom line that will come later
0618     for (int i = 0; i < m_horizontalLineCount; i++) {
0619         int lineY = ceil(i * lineSpacing) + 1; // floor +1 makes the entry at point 0 on pixel 1
0620         vertices << QVector2D(0, lineY) << QVector2D(width(), lineY);
0621     }
0622     // bottom line
0623     vertices << QVector2D(0, height() - 1) << QVector2D(width(), height() - 1);
0624 
0625     // Tessellate
0626     float min = height();
0627     float max = height();
0628 
0629     QHash<PlotData *, QPair<int, int>> verticesCounts;
0630 
0631     // encase all m_plotData access in a mutex, since rendering is usually done in another thread
0632     m_mutex.lock();
0633     int roundedHeight = qRound(height());
0634     int roundedWidth = qRound(width());
0635 
0636     for (auto data : std::as_const(m_plotData)) {
0637         // Interpolate the data set
0638         const QPainterPath path = interpolate(data->m_normalizedValues, 0, roundedWidth);
0639 
0640         // Flatten the path
0641         const QList<QPolygonF> polygons = path.toSubpathPolygons();
0642 
0643         for (const QPolygonF &p : polygons) {
0644             verticesCounts[data].first = 0;
0645             vertices << QVector2D(p.first().x(), roundedHeight);
0646 
0647             for (int i = 0; i < p.count() - 1; i++) {
0648                 min = qMin<float>(min, roundedHeight - p[i].y());
0649                 vertices << QVector2D(p[i].x(), roundedHeight - p[i].y());
0650                 vertices << QVector2D((p[i].x() + p[i + 1].x()) / 2.0, roundedHeight);
0651                 verticesCounts[data].first += 2;
0652             }
0653 
0654             min = qMin<float>(min, roundedHeight - p.last().y());
0655             vertices << QVector2D(p.last().x(), roundedHeight - p.last().y());
0656             vertices << QVector2D(p.last().x(), roundedHeight);
0657             verticesCounts[data].first += 3;
0658         }
0659 
0660         for (const QPolygonF &p : polygons) {
0661             verticesCounts[data].second = 0;
0662 
0663             for (int i = 0; i < p.count() - 1; i++) {
0664                 min = qMin<float>(min, roundedHeight - p[i].y());
0665                 vertices << QVector2D(p[i].x(), roundedHeight - p[i].y());
0666                 verticesCounts[data].second += 1;
0667             }
0668 
0669             vertices << QVector2D(p.last().x(), roundedHeight - p.last().y());
0670             verticesCounts[data].second += 1;
0671             min = qMin<float>(min, roundedHeight - p.last().y());
0672         }
0673     }
0674     m_mutex.unlock();
0675 
0676     // Upload vertices
0677     GLuint vbo;
0678     glGenBuffers(1, &vbo);
0679 
0680     glBindBuffer(GL_ARRAY_BUFFER, vbo);
0681     glBufferData(GL_ARRAY_BUFFER, vertices.count() * sizeof(QVector2D), vertices.constData(), GL_STATIC_DRAW);
0682 
0683     // Set up the array
0684     glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(QVector2D), nullptr);
0685     glEnableVertexAttribArray(0);
0686 
0687     // Bind the shader program
0688     m_node->bind();
0689     m_node->setMatrix(m_matrix);
0690 
0691     // Draw the lines
0692     QColor color1 = m_gridColor;
0693     QColor color2 = m_gridColor;
0694     color1.setAlphaF(0.10);
0695     color2.setAlphaF(0.40);
0696     m_node->setYMin((float)0.0);
0697     m_node->setYMax((float)height());
0698     m_node->setColor1(color1);
0699     m_node->setColor2(color2);
0700 
0701     glDrawArrays(GL_LINES, 0, (m_horizontalLineCount + 1) * 2);
0702 
0703     // Enable alpha blending
0704     glEnable(GL_BLEND);
0705     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
0706 
0707     QPair<int, int> oldCount;
0708     m_mutex.lock();
0709     for (auto data : std::as_const(m_plotData)) {
0710         color2 = data->color();
0711         color2.setAlphaF(0.60);
0712         // Draw the graph
0713         m_node->setYMin(min);
0714         m_node->setYMax(max);
0715         m_node->setColor1(data->color());
0716         m_node->setColor2(color2);
0717 
0718         //+2 is for the bottom line
0719         glDrawArrays(GL_TRIANGLE_STRIP, m_horizontalLineCount * 2 + 2 + oldCount.first + oldCount.second, verticesCounts[data].first);
0720 
0721         oldCount.first += verticesCounts[data].first;
0722 
0723         m_node->setColor1(data->color());
0724         m_node->setColor2(data->color());
0725         glDrawArrays(GL_LINE_STRIP, m_horizontalLineCount * 2 + 2 + oldCount.first + oldCount.second, verticesCounts[data].second);
0726 
0727         oldCount.second += verticesCounts[data].second;
0728     }
0729     m_mutex.unlock();
0730 
0731     glDisable(GL_BLEND);
0732 
0733     m_node->setColor1(m_gridColor);
0734     m_node->setColor2(m_gridColor);
0735     glDrawArrays(GL_LINES, vertices.count() - 2, 2);
0736 
0737     if (m_haveMSAA && m_haveFramebufferBlit) {
0738         // Resolve the MSAA buffer
0739         glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo);
0740         glBindFramebuffer(GL_DRAW_FRAMEBUFFER, static_cast<PlotTexture *>(m_node->texture())->fbo());
0741         glBlitFramebuffer(0, 0, width(), height(), 0, 0, width(), height(), GL_COLOR_BUFFER_BIT, GL_NEAREST);
0742 
0743         // Delete the render buffer
0744         glDeleteRenderbuffers(1, &rb);
0745     }
0746 
0747     // Delete the VBO
0748     glDeleteBuffers(1, &vbo);
0749 }
0750 
0751 QSGNode *Plotter::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData)
0752 {
0753     Q_UNUSED(updatePaintNodeData)
0754     if (!window()->openglContext()) {
0755         delete oldNode;
0756         return nullptr;
0757     }
0758 
0759     PlotSGNode *n = static_cast<PlotSGNode *>(oldNode);
0760 
0761     if (width() == 0 && height() == 0) {
0762         delete oldNode;
0763         return nullptr;
0764     }
0765 
0766     if (!n) {
0767         n = new PlotSGNode();
0768         n->setTexture(new PlotTexture(window()->openglContext()));
0769         n->setFiltering(QSGTexture::Linear);
0770 
0771         m_node = n;
0772         if (m_window) {
0773             disconnect(m_window.data(), &QQuickWindow::beforeRendering, this, &Plotter::render);
0774         }
0775         connect(window(), &QQuickWindow::beforeRendering, this, &Plotter::render, Qt::DirectConnection);
0776         m_window = window();
0777     }
0778 
0779     if (!m_initialized) {
0780         glGenFramebuffers(1, &m_fbo);
0781 
0782         QOpenGLContext *ctx = window()->openglContext();
0783         QPair<int, int> version = ctx->format().version();
0784 
0785         if (ctx->isOpenGLES()) {
0786             m_haveMSAA = version >= qMakePair(3, 0) || ctx->hasExtension("GL_NV_framebuffer_multisample");
0787             m_haveFramebufferBlit = version >= qMakePair(3, 0) || ctx->hasExtension("GL_NV_framebuffer_blit");
0788             m_haveInternalFormatQuery = version >= qMakePair(3, 0);
0789             m_internalFormat = version >= qMakePair(3, 0) ? GL_RGBA8 : GL_RGBA;
0790         } else {
0791             /* clang-format off */
0792             m_haveMSAA = version >= qMakePair(3, 2)
0793                          || ctx->hasExtension("GL_ARB_framebuffer_object")
0794                          || ctx->hasExtension("GL_EXT_framebuffer_multisample");
0795 
0796             m_haveFramebufferBlit = version >= qMakePair(3, 0)
0797                                     || ctx->hasExtension("GL_ARB_framebuffer_object")
0798                                     || ctx->hasExtension("GL_EXT_framebuffer_blit");
0799             /* clang-format on */
0800 
0801             m_haveInternalFormatQuery = version >= qMakePair(4, 2) || ctx->hasExtension("GL_ARB_internalformat_query");
0802             m_internalFormat = GL_RGBA8;
0803         }
0804 
0805         // Query the maximum sample count for the internal format
0806         if (m_haveInternalFormatQuery) {
0807             int count = 0;
0808             glGetInternalformativ(GL_RENDERBUFFER, m_internalFormat, GL_NUM_SAMPLE_COUNTS, 1, &count);
0809 
0810             if (count > 0) {
0811                 QVector<int> samples(count);
0812                 glGetInternalformativ(GL_RENDERBUFFER, m_internalFormat, GL_SAMPLES, count, samples.data());
0813 
0814                 // The samples are returned in descending order. Choose the highest value.
0815                 m_samples = samples.at(0);
0816             } else {
0817                 m_samples = 0;
0818             }
0819         } else if (m_haveMSAA) {
0820             glGetIntegerv(GL_MAX_SAMPLES, &m_samples);
0821         } else {
0822             m_samples = 0;
0823         }
0824 
0825         m_initialized = true;
0826     }
0827 
0828     // we need a size always equal or smaller, size.toSize() won't do
0829     const QSize targetTextureSize(qRound(boundingRect().size().width()), qRound(boundingRect().size().height()));
0830     if (n->texture()->textureSize() != targetTextureSize) {
0831         static_cast<PlotTexture *>(n->texture())->recreate(targetTextureSize);
0832         m_matrix = QMatrix4x4();
0833         m_matrix.ortho(0, targetTextureSize.width(), 0, targetTextureSize.height(), -1, 1);
0834     }
0835 
0836     n->setRect(QRect(QPoint(0, 0), targetTextureSize));
0837     return n;
0838 }
0839 
0840 void Plotter::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
0841 {
0842     QQuickItem::geometryChanged(newGeometry, oldGeometry);
0843     normalizeData();
0844 }
0845 
0846 void Plotter::normalizeData()
0847 {
0848     if (m_plotData.isEmpty()) {
0849         return;
0850     }
0851     // normalize data
0852     m_max = std::numeric_limits<qreal>::min();
0853     m_min = std::numeric_limits<qreal>::max();
0854     qreal adjustedMax = m_max;
0855     qreal adjustedMin = m_min;
0856     m_mutex.lock();
0857     if (m_stacked) {
0858         PlotData *previousData = nullptr;
0859         auto i = m_plotData.constEnd();
0860         do {
0861             --i;
0862             PlotData *data = *i;
0863             data->m_normalizedValues.clear();
0864             data->m_normalizedValues.resize(data->values().count());
0865             if (previousData) {
0866                 for (int i = 0; i < data->values().count(); ++i) {
0867                     data->m_normalizedValues[i] = data->values().value(i) + previousData->m_normalizedValues.value(i);
0868 
0869                     if (data->m_normalizedValues[i] > adjustedMax) {
0870                         adjustedMax = data->m_normalizedValues[i];
0871                     }
0872                     if (data->m_normalizedValues[i] < adjustedMin) {
0873                         adjustedMin = data->m_normalizedValues[i];
0874                     }
0875                 }
0876             } else {
0877                 data->m_normalizedValues = data->values().toVector();
0878                 if (data->max() > adjustedMax) {
0879                     adjustedMax = data->max();
0880                 }
0881                 if (data->min() < adjustedMin) {
0882                     adjustedMin = data->min();
0883                 }
0884             }
0885             previousData = data;
0886 
0887             // global max and global min
0888             if (data->max() > m_max) {
0889                 m_max = data->max();
0890             }
0891             if (data->min() < m_min) {
0892                 m_min = data->min();
0893             }
0894         } while (i != m_plotData.constBegin());
0895 
0896     } else {
0897         for (auto data : std::as_const(m_plotData)) {
0898             data->m_normalizedValues.clear();
0899             data->m_normalizedValues = data->values().toVector();
0900             // global max and global min
0901             if (data->max() > m_max) {
0902                 adjustedMax = m_max = data->max();
0903             }
0904             if (data->min() < m_min) {
0905                 adjustedMin = m_min = data->min();
0906             }
0907         }
0908     }
0909     m_mutex.unlock();
0910 
0911     if (adjustedMin > 0.0 && adjustedMax > 0.0) {
0912         adjustedMin = 0.0;
0913     }
0914 
0915     if (adjustedMin < 0.0 && adjustedMax < 0.0) {
0916         adjustedMax = 0.0;
0917     }
0918 
0919     if (m_autoRange || m_rangeMax > m_rangeMin) {
0920         if (!m_autoRange) {
0921             adjustedMax = m_rangeMax;
0922             adjustedMin = m_rangeMin;
0923         }
0924 
0925         qreal adjust;
0926         // this should never happen, remove?
0927         if (qFuzzyCompare(adjustedMax - adjustedMin, std::numeric_limits<qreal>::min())) {
0928             adjust = 1;
0929         } else {
0930             adjust = (height() / (adjustedMax - adjustedMin));
0931         }
0932 
0933         // normalizebased on global max and min
0934         m_mutex.lock();
0935         for (auto data : std::as_const(m_plotData)) {
0936             for (int i = 0; i < data->values().count(); ++i) {
0937                 data->m_normalizedValues[i] = (data->m_normalizedValues.value(i) - adjustedMin) * adjust;
0938             }
0939         }
0940         m_mutex.unlock();
0941     }
0942 }
0943 
0944 #include "moc_plotter.cpp"