presentation/GraphPaintCache.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "GraphPaintCache.h" | ||
| 2 | |||
| 3 | #include <QImage> | ||
| 4 | #include <QLoggingCategory> | ||
| 5 | #include <QPainter> | ||
| 6 | #include <QQuickItem> | ||
| 7 | #include <QQuickWindow> | ||
| 8 | #include <QSGGeometryNode> | ||
| 9 | #include <QSGImageNode> | ||
| 10 | #include <QSGRendererInterface> | ||
| 11 | #include <QSGTransformNode> | ||
| 12 | #include <QSGVertexColorMaterial> | ||
| 13 | |||
| 14 | #include <cmath> | ||
| 15 | #include <cstring> | ||
| 16 | |||
| 17 | Q_DECLARE_LOGGING_CATEGORY(lcGraphLines) | ||
| 18 | |||
| 19 | namespace { | ||
| 20 | |||
| 21 | ✗ | QSGGeometryNode *meshNode(QSGGeometryNode *node, const GraphGeometry::Mesh &mesh) { | |
| 22 | ✗ | if (!node) { | |
| 23 | ✗ | node = new QSGGeometryNode; | |
| 24 | ✗ | auto *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_ColoredPoint2D(), 0, 0, QSGGeometry::UnsignedIntType); | |
| 25 | ✗ | geometry->setDrawingMode(QSGGeometry::DrawTriangles); | |
| 26 | ✗ | node->setGeometry(geometry); | |
| 27 | ✗ | node->setFlag(QSGNode::OwnsGeometry); | |
| 28 | ✗ | node->setMaterial(new QSGVertexColorMaterial); | |
| 29 | ✗ | node->setFlag(QSGNode::OwnsMaterial); | |
| 30 | } | ||
| 31 | ✗ | QSGGeometry *geometry = node->geometry(); | |
| 32 | ✗ | const auto &vertices = mesh.vertices(); | |
| 33 | ✗ | const auto &indices = mesh.indices(); | |
| 34 | ✗ | geometry->allocate(int(vertices.size()), int(indices.size())); | |
| 35 | static_assert(sizeof(GraphGeometry::Vertex) == sizeof(QSGGeometry::ColoredPoint2D)); | ||
| 36 | ✗ | if (!vertices.empty()) | |
| 37 | ✗ | std::memcpy(geometry->vertexDataAsColoredPoint2D(), vertices.data(), vertices.size() * sizeof(GraphGeometry::Vertex)); | |
| 38 | ✗ | if (!indices.empty()) | |
| 39 | ✗ | std::memcpy(geometry->indexDataAsUInt(), indices.data(), indices.size() * sizeof(quint32)); | |
| 40 | ✗ | node->markDirty(QSGNode::DirtyGeometry); | |
| 41 | ✗ | return node; | |
| 42 | } | ||
| 43 | |||
| 44 | } // namespace | ||
| 45 | |||
| 46 | 859 | QSGNode *GraphPaintCache::update(QQuickItem *item, QSGNode *old, double k, QPointF t, bool contentChanged, | |
| 47 | const Draw &draw, int *vertices, bool *refresh) { | ||
| 48 | 859 | *refresh = false; | |
| 49 |
1/1✓ Branch 2 → 3 taken 859 times.
|
859 | QQuickWindow *window = item->window(); |
| 50 |
3/5✓ Branch 3 → 4 taken 859 times.
✗ Branch 3 → 10 not taken.
✓ Branch 4 → 5 taken 859 times.
✓ Branch 5 → 6 taken 859 times.
✗ Branch 5 → 10 not taken.
|
1718 | const bool software = window && window->rendererInterface() && |
| 51 |
3/4✓ Branch 6 → 7 taken 859 times.
✓ Branch 7 → 8 taken 859 times.
✓ Branch 8 → 9 taken 859 times.
✗ Branch 8 → 10 not taken.
|
859 | window->rendererInterface()->graphicsApi() == QSGRendererInterface::Software; |
| 52 |
2/2✓ Branch 11 → 12 taken 859 times.
✓ Branch 12 → 13 taken 859 times.
|
859 | const QSizeF size(item->width(), item->height()); |
| 53 | // A node of the other kind (the renderer changed): start over. | ||
| 54 |
3/4✓ Branch 14 → 15 taken 820 times.
✓ Branch 14 → 19 taken 39 times.
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 19 taken 820 times.
|
859 | if (old && software != m_software) { |
| 55 | ✗ | delete old; | |
| 56 | ✗ | old = nullptr; | |
| 57 | ✗ | m_valid = false; | |
| 58 | } | ||
| 59 | 859 | m_software = software; | |
| 60 | 859 | const double extent = std::max(size.width(), size.height()); | |
| 61 |
7/8✓ Branch 22 → 23 taken 820 times.
✓ Branch 22 → 28 taken 39 times.
✓ Branch 23 → 24 taken 820 times.
✗ Branch 23 → 28 not taken.
✓ Branch 24 → 25 taken 733 times.
✓ Branch 24 → 28 taken 87 times.
✓ Branch 26 → 27 taken 732 times.
✓ Branch 26 → 28 taken 1 time.
|
859 | const bool reusable = old && m_valid && !contentChanged && size == m_size; |
| 62 | 859 | const QPointF moved = t - m_t; | |
| 63 |
5/6✓ Branch 30 → 31 taken 732 times.
✓ Branch 30 → 40 taken 127 times.
✓ Branch 32 → 33 taken 5 times.
✓ Branch 32 → 40 taken 727 times.
✓ Branch 35 → 36 taken 5 times.
✗ Branch 35 → 40 not taken.
|
864 | const bool panOnly = reusable && std::abs(k - m_k) < 1e-9 && std::abs(moved.x()) <= 0.2 * extent && |
| 64 |
1/2✓ Branch 38 → 39 taken 5 times.
✗ Branch 38 → 40 not taken.
|
5 | std::abs(moved.y()) <= 0.2 * extent; |
| 65 |
1/2✗ Branch 41 → 42 not taken.
✓ Branch 41 → 70 taken 859 times.
|
859 | if (!software) { |
| 66 | ✗ | auto *transform = static_cast<QSGTransformNode *>(old); | |
| 67 | ✗ | if (panOnly) { | |
| 68 | ✗ | QMatrix4x4 shift; | |
| 69 | ✗ | shift.translate(float(moved.x()), float(moved.y())); | |
| 70 | ✗ | transform->setMatrix(shift); | |
| 71 | ✗ | return transform; | |
| 72 | } | ||
| 73 | ✗ | if (!transform) | |
| 74 | ✗ | transform = new QSGTransformNode; | |
| 75 | ✗ | GraphGeometry::Mesh mesh; | |
| 76 | ✗ | const double margin = 0.25 * extent; | |
| 77 | ✗ | mesh.setViewport(QRectF(QPointF(0, 0), size).adjusted(-margin, -margin, margin, margin)); | |
| 78 | ✗ | draw(&mesh); | |
| 79 | ✗ | *vertices = int(mesh.vertices().size()); | |
| 80 | ✗ | auto *geometry = static_cast<QSGGeometryNode *>(transform->firstChild()); | |
| 81 | ✗ | const bool fresh = !geometry; | |
| 82 | ✗ | geometry = meshNode(geometry, mesh); | |
| 83 | ✗ | if (fresh) | |
| 84 | ✗ | transform->appendChildNode(geometry); | |
| 85 | ✗ | transform->setMatrix(QMatrix4x4()); | |
| 86 | ✗ | m_valid = true; | |
| 87 | ✗ | m_k = k; | |
| 88 | ✗ | m_t = t; | |
| 89 | ✗ | m_size = size; | |
| 90 | ✗ | return transform; | |
| 91 | ✗ | } | |
| 92 | 859 | auto *image = static_cast<QSGImageNode *>(old); | |
| 93 |
2/2✓ Branch 70 → 71 taken 5 times.
✓ Branch 70 → 74 taken 854 times.
|
859 | if (panOnly) { |
| 94 |
1/1✓ Branch 72 → 73 taken 5 times.
|
5 | image->setRect(m_drawn.translated(moved)); |
| 95 | 5 | return image; | |
| 96 | } | ||
| 97 | // A zoom in progress: the last picture, scaled to where this view puts it | ||
| 98 | // (canvas = world·k + t, the picture drawn at k₀, t₀), until the view rests. | ||
| 99 |
5/6✓ Branch 74 → 75 taken 727 times.
✓ Branch 74 → 86 taken 127 times.
✓ Branch 75 → 76 taken 726 times.
✓ Branch 75 → 86 taken 1 time.
✓ Branch 76 → 77 taken 726 times.
✗ Branch 76 → 86 not taken.
|
854 | if (reusable && k / m_k > 0.5 && k / m_k < 2) { |
| 100 | 726 | const double ratio = k / m_k; | |
| 101 | 726 | const QPointF topLeft = (m_drawn.topLeft() - m_t) * ratio + t; | |
| 102 |
1/1✓ Branch 84 → 85 taken 726 times.
|
726 | image->setRect(QRectF(topLeft, m_drawn.size() * ratio)); |
| 103 | 726 | *refresh = true; | |
| 104 | 726 | return image; | |
| 105 | } | ||
| 106 |
2/2✓ Branch 86 → 87 taken 39 times.
✓ Branch 86 → 89 taken 89 times.
|
128 | if (!image) { |
| 107 |
1/1✓ Branch 87 → 88 taken 39 times.
|
39 | image = window->createImageNode(); |
| 108 |
1/1✓ Branch 88 → 89 taken 39 times.
|
39 | image->setOwnsTexture(true); |
| 109 | } | ||
| 110 | 128 | const double margin = 0.25 * extent; | |
| 111 | 128 | const QRectF drawn = QRectF(QPointF(0, 0), size).adjusted(-margin, -margin, margin, margin); | |
| 112 | // S106-945: at most MaxPicturePixels (64 MiB ARGB32) per item — a large | ||
| 113 | // HiDPI window asked for ~370 MB per item; past the cap the picture is | ||
| 114 | // drawn at a lower resolution, and a failed allocation draws nothing. | ||
| 115 |
1/1✓ Branch 92 → 93 taken 128 times.
|
128 | qreal dpr = window->effectiveDevicePixelRatio(); |
| 116 | 128 | const double wanted = drawn.width() * drawn.height() * dpr * dpr; | |
| 117 |
1/2✗ Branch 95 → 96 not taken.
✓ Branch 95 → 97 taken 128 times.
|
128 | if (wanted > double(MaxPicturePixels)) |
| 118 | ✗ | dpr *= std::sqrt(double(MaxPicturePixels) / wanted); | |
| 119 | 128 | const QSize pixels = (drawn.size() * dpr).toSize(); | |
| 120 |
1/2✗ Branch 101 → 102 not taken.
✓ Branch 101 → 105 taken 128 times.
|
128 | if (pixels.isEmpty()) { |
| 121 | ✗ | image->setRect(QRectF()); | |
| 122 | ✗ | return image; | |
| 123 | } | ||
| 124 |
1/1✓ Branch 105 → 106 taken 128 times.
|
128 | QImage picture(pixels, QImage::Format_ARGB32_Premultiplied); |
| 125 |
2/3✓ Branch 106 → 107 taken 128 times.
✗ Branch 107 → 108 not taken.
✓ Branch 107 → 125 taken 128 times.
|
128 | if (picture.isNull()) { |
| 126 | ✗ | qCWarning(lcGraphLines) << "Graph picture not allocated:" << pixels; | |
| 127 | ✗ | image->setRect(QRectF()); | |
| 128 | ✗ | return image; | |
| 129 | } | ||
| 130 |
1/1✓ Branch 125 → 126 taken 128 times.
|
128 | picture.setDevicePixelRatio(dpr); |
| 131 |
1/1✓ Branch 126 → 127 taken 128 times.
|
128 | picture.fill(Qt::transparent); |
| 132 | { | ||
| 133 |
1/1✓ Branch 127 → 128 taken 128 times.
|
128 | QPainter painter(&picture); |
| 134 |
1/1✓ Branch 130 → 131 taken 128 times.
|
128 | painter.translate(-drawn.topLeft()); |
| 135 | 128 | GraphGeometry::PainterSink sink(&painter); | |
| 136 | 128 | sink.setViewport(drawn); | |
| 137 |
1/1✓ Branch 133 → 134 taken 128 times.
|
128 | draw(&sink); |
| 138 | 128 | } | |
| 139 | 128 | *vertices = 0; | |
| 140 |
2/2✓ Branch 136 → 137 taken 128 times.
✓ Branch 137 → 138 taken 128 times.
|
128 | image->setTexture(window->createTextureFromImage(picture)); |
| 141 |
1/1✓ Branch 138 → 139 taken 128 times.
|
128 | image->setRect(drawn); |
| 142 | 128 | m_valid = true; | |
| 143 | 128 | m_k = k; | |
| 144 | 128 | m_t = t; | |
| 145 | 128 | m_size = size; | |
| 146 | 128 | m_drawn = drawn; | |
| 147 | 128 | return image; | |
| 148 | 128 | } | |
| 149 |