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"