| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. |
| 4 | ** Contact: https://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the QtQuick module of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:LGPL$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at https://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** GNU Lesser General Public License Usage |
| 18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
| 19 | ** General Public License version 3 as published by the Free Software |
| 20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| 21 | ** packaging of this file. Please review the following information to |
| 22 | ** ensure the GNU Lesser General Public License version 3 requirements |
| 23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| 24 | ** |
| 25 | ** GNU General Public License Usage |
| 26 | ** Alternatively, this file may be used under the terms of the GNU |
| 27 | ** General Public License version 2.0 or (at your option) the GNU General |
| 28 | ** Public license version 3 or any later version approved by the KDE Free |
| 29 | ** Qt Foundation. The licenses are as published by the Free Software |
| 30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| 31 | ** included in the packaging of this file. Please review the following |
| 32 | ** information to ensure the GNU General Public License requirements will |
| 33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| 34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
| 35 | ** |
| 36 | ** $QT_END_LICENSE$ |
| 37 | ** |
| 38 | ****************************************************************************/ |
| 39 | |
| 40 | #include "qquickshapenvprrenderer_p.h" |
| 41 | #include <QOpenGLExtraFunctions> |
| 42 | #include <QOpenGLFramebufferObject> |
| 43 | #include <QOpenGLShaderProgram> |
| 44 | #include <QOpenGLBuffer> |
| 45 | #include <qmath.h> |
| 46 | #include <private/qpainterpath_p.h> |
| 47 | #include <private/qquickpath_p_p.h> |
| 48 | |
| 49 | QT_BEGIN_NAMESPACE |
| 50 | |
| 51 | void QQuickShapeNvprRenderer::beginSync(int totalCount) |
| 52 | { |
| 53 | if (m_sp.count() != totalCount) { |
| 54 | m_sp.resize(asize: totalCount); |
| 55 | m_accDirty |= DirtyList; |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | void QQuickShapeNvprRenderer::setPath(int index, const QQuickPath *path) |
| 60 | { |
| 61 | ShapePathGuiData &d(m_sp[index]); |
| 62 | convertPath(path, d: &d); |
| 63 | d.dirty |= DirtyPath; |
| 64 | m_accDirty |= DirtyPath; |
| 65 | } |
| 66 | |
| 67 | void QQuickShapeNvprRenderer::setStrokeColor(int index, const QColor &color) |
| 68 | { |
| 69 | ShapePathGuiData &d(m_sp[index]); |
| 70 | d.strokeColor = color; |
| 71 | d.dirty |= DirtyStyle; |
| 72 | m_accDirty |= DirtyStyle; |
| 73 | } |
| 74 | |
| 75 | void QQuickShapeNvprRenderer::setStrokeWidth(int index, qreal w) |
| 76 | { |
| 77 | ShapePathGuiData &d(m_sp[index]); |
| 78 | d.strokeWidth = w; |
| 79 | d.dirty |= DirtyStyle; |
| 80 | m_accDirty |= DirtyStyle; |
| 81 | } |
| 82 | |
| 83 | void QQuickShapeNvprRenderer::setFillColor(int index, const QColor &color) |
| 84 | { |
| 85 | ShapePathGuiData &d(m_sp[index]); |
| 86 | d.fillColor = color; |
| 87 | d.dirty |= DirtyStyle; |
| 88 | m_accDirty |= DirtyStyle; |
| 89 | } |
| 90 | |
| 91 | void QQuickShapeNvprRenderer::setFillRule(int index, QQuickShapePath::FillRule fillRule) |
| 92 | { |
| 93 | ShapePathGuiData &d(m_sp[index]); |
| 94 | d.fillRule = fillRule; |
| 95 | d.dirty |= DirtyFillRule; |
| 96 | m_accDirty |= DirtyFillRule; |
| 97 | } |
| 98 | |
| 99 | void QQuickShapeNvprRenderer::setJoinStyle(int index, QQuickShapePath::JoinStyle joinStyle, int miterLimit) |
| 100 | { |
| 101 | ShapePathGuiData &d(m_sp[index]); |
| 102 | d.joinStyle = joinStyle; |
| 103 | d.miterLimit = miterLimit; |
| 104 | d.dirty |= DirtyStyle; |
| 105 | m_accDirty |= DirtyStyle; |
| 106 | } |
| 107 | |
| 108 | void QQuickShapeNvprRenderer::setCapStyle(int index, QQuickShapePath::CapStyle capStyle) |
| 109 | { |
| 110 | ShapePathGuiData &d(m_sp[index]); |
| 111 | d.capStyle = capStyle; |
| 112 | d.dirty |= DirtyStyle; |
| 113 | m_accDirty |= DirtyStyle; |
| 114 | } |
| 115 | |
| 116 | void QQuickShapeNvprRenderer::setStrokeStyle(int index, QQuickShapePath::StrokeStyle strokeStyle, |
| 117 | qreal dashOffset, const QVector<qreal> &dashPattern) |
| 118 | { |
| 119 | ShapePathGuiData &d(m_sp[index]); |
| 120 | d.dashActive = strokeStyle == QQuickShapePath::DashLine; |
| 121 | d.dashOffset = dashOffset; |
| 122 | d.dashPattern = dashPattern; |
| 123 | d.dirty |= DirtyDash; |
| 124 | m_accDirty |= DirtyDash; |
| 125 | } |
| 126 | |
| 127 | void QQuickShapeNvprRenderer::setFillGradient(int index, QQuickShapeGradient *gradient) |
| 128 | { |
| 129 | ShapePathGuiData &d(m_sp[index]); |
| 130 | if (gradient) { |
| 131 | d.fillGradient.stops = gradient->gradientStops(); // sorted |
| 132 | d.fillGradient.spread = gradient->spread(); |
| 133 | if (QQuickShapeLinearGradient *g = qobject_cast<QQuickShapeLinearGradient *>(object: gradient)) { |
| 134 | d.fillGradientActive = LinearGradient; |
| 135 | d.fillGradient.a = QPointF(g->x1(), g->y1()); |
| 136 | d.fillGradient.b = QPointF(g->x2(), g->y2()); |
| 137 | } else if (QQuickShapeRadialGradient *g = qobject_cast<QQuickShapeRadialGradient *>(object: gradient)) { |
| 138 | d.fillGradientActive = RadialGradient; |
| 139 | d.fillGradient.a = QPointF(g->centerX(), g->centerY()); |
| 140 | d.fillGradient.b = QPointF(g->focalX(), g->focalY()); |
| 141 | d.fillGradient.v0 = g->centerRadius(); |
| 142 | d.fillGradient.v1 = g->focalRadius(); |
| 143 | } else if (QQuickShapeConicalGradient *g = qobject_cast<QQuickShapeConicalGradient *>(object: gradient)) { |
| 144 | d.fillGradientActive = ConicalGradient; |
| 145 | d.fillGradient.a = QPointF(g->centerX(), g->centerY()); |
| 146 | d.fillGradient.v0 = g->angle(); |
| 147 | } else { |
| 148 | Q_UNREACHABLE(); |
| 149 | } |
| 150 | } else { |
| 151 | d.fillGradientActive = NoGradient; |
| 152 | } |
| 153 | d.dirty |= DirtyFillGradient; |
| 154 | m_accDirty |= DirtyFillGradient; |
| 155 | } |
| 156 | |
| 157 | void QQuickShapeNvprRenderer::endSync(bool) |
| 158 | { |
| 159 | } |
| 160 | |
| 161 | void QQuickShapeNvprRenderer::setNode(QQuickShapeNvprRenderNode *node) |
| 162 | { |
| 163 | if (m_node != node) { |
| 164 | m_node = node; |
| 165 | m_accDirty |= DirtyList; |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | QDebug operator<<(QDebug debug, const QQuickShapeNvprRenderer::NvprPath &path) |
| 170 | { |
| 171 | QDebugStateSaver saver(debug); |
| 172 | debug.space().noquote(); |
| 173 | if (!path.str.isEmpty()) { |
| 174 | debug << "Path with SVG string" << path.str; |
| 175 | return debug; |
| 176 | } |
| 177 | debug << "Path with" << path.cmd.count() << "commands" ; |
| 178 | int ci = 0; |
| 179 | for (GLubyte cmd : path.cmd) { |
| 180 | static struct { GLubyte cmd; const char *s; int coordCount; } nameTabs[] = { |
| 181 | { GL_MOVE_TO_NV, .s: "moveTo" , .coordCount: 2 }, |
| 182 | { GL_LINE_TO_NV, .s: "lineTo" , .coordCount: 2 }, |
| 183 | { GL_QUADRATIC_CURVE_TO_NV, .s: "quadTo" , .coordCount: 4 }, |
| 184 | { GL_CUBIC_CURVE_TO_NV, .s: "cubicTo" , .coordCount: 6 }, |
| 185 | { GL_LARGE_CW_ARC_TO_NV, .s: "arcTo-large-CW" , .coordCount: 5 }, |
| 186 | { GL_LARGE_CCW_ARC_TO_NV, .s: "arcTo-large-CCW" , .coordCount: 5 }, |
| 187 | { GL_SMALL_CW_ARC_TO_NV, .s: "arcTo-small-CW" , .coordCount: 5 }, |
| 188 | { GL_SMALL_CCW_ARC_TO_NV, .s: "arcTo-small-CCW" , .coordCount: 5 }, |
| 189 | { GL_CLOSE_PATH_NV, .s: "closePath" , .coordCount: 0 } }; |
| 190 | for (const auto &nameTab : nameTabs) { |
| 191 | if (nameTab.cmd == cmd) { |
| 192 | QByteArray cs; |
| 193 | for (int j = 0; j < nameTab.coordCount; ++j) { |
| 194 | cs.append(a: QByteArray::number(path.coord[ci++])); |
| 195 | cs.append(c: ' '); |
| 196 | } |
| 197 | debug << "\n " << nameTab.s << " " << cs; |
| 198 | break; |
| 199 | } |
| 200 | } |
| 201 | } |
| 202 | return debug; |
| 203 | } |
| 204 | |
| 205 | static inline void appendCoords(QVector<GLfloat> *v, QQuickCurve *c, QPointF *pos) |
| 206 | { |
| 207 | QPointF p(c->hasRelativeX() ? pos->x() + c->relativeX() : c->x(), |
| 208 | c->hasRelativeY() ? pos->y() + c->relativeY() : c->y()); |
| 209 | v->append(t: p.x()); |
| 210 | v->append(t: p.y()); |
| 211 | *pos = p; |
| 212 | } |
| 213 | |
| 214 | static inline void appendControlCoords(QVector<GLfloat> *v, QQuickPathQuad *c, const QPointF &pos) |
| 215 | { |
| 216 | QPointF p(c->hasRelativeControlX() ? pos.x() + c->relativeControlX() : c->controlX(), |
| 217 | c->hasRelativeControlY() ? pos.y() + c->relativeControlY() : c->controlY()); |
| 218 | v->append(t: p.x()); |
| 219 | v->append(t: p.y()); |
| 220 | } |
| 221 | |
| 222 | static inline void appendControl1Coords(QVector<GLfloat> *v, QQuickPathCubic *c, const QPointF &pos) |
| 223 | { |
| 224 | QPointF p(c->hasRelativeControl1X() ? pos.x() + c->relativeControl1X() : c->control1X(), |
| 225 | c->hasRelativeControl1Y() ? pos.y() + c->relativeControl1Y() : c->control1Y()); |
| 226 | v->append(t: p.x()); |
| 227 | v->append(t: p.y()); |
| 228 | } |
| 229 | |
| 230 | static inline void appendControl2Coords(QVector<GLfloat> *v, QQuickPathCubic *c, const QPointF &pos) |
| 231 | { |
| 232 | QPointF p(c->hasRelativeControl2X() ? pos.x() + c->relativeControl2X() : c->control2X(), |
| 233 | c->hasRelativeControl2Y() ? pos.y() + c->relativeControl2Y() : c->control2Y()); |
| 234 | v->append(t: p.x()); |
| 235 | v->append(t: p.y()); |
| 236 | } |
| 237 | |
| 238 | void QQuickShapeNvprRenderer::convertPath(const QQuickPath *path, ShapePathGuiData *d) |
| 239 | { |
| 240 | d->path = NvprPath(); |
| 241 | if (!path) |
| 242 | return; |
| 243 | |
| 244 | const QList<QQuickPathElement *> &pp(QQuickPathPrivate::get(path)->_pathElements); |
| 245 | if (pp.isEmpty()) |
| 246 | return; |
| 247 | |
| 248 | QPointF startPos(path->startX(), path->startY()); |
| 249 | QPointF pos(startPos); |
| 250 | if (!qFuzzyIsNull(d: pos.x()) || !qFuzzyIsNull(d: pos.y())) { |
| 251 | d->path.cmd.append(GL_MOVE_TO_NV); |
| 252 | d->path.coord.append(t: pos.x()); |
| 253 | d->path.coord.append(t: pos.y()); |
| 254 | } |
| 255 | |
| 256 | for (QQuickPathElement *e : pp) { |
| 257 | if (QQuickPathMove *o = qobject_cast<QQuickPathMove *>(object: e)) { |
| 258 | d->path.cmd.append(GL_MOVE_TO_NV); |
| 259 | appendCoords(v: &d->path.coord, c: o, pos: &pos); |
| 260 | startPos = pos; |
| 261 | } else if (QQuickPathLine *o = qobject_cast<QQuickPathLine *>(object: e)) { |
| 262 | d->path.cmd.append(GL_LINE_TO_NV); |
| 263 | appendCoords(v: &d->path.coord, c: o, pos: &pos); |
| 264 | } else if (QQuickPathQuad *o = qobject_cast<QQuickPathQuad *>(object: e)) { |
| 265 | d->path.cmd.append(GL_QUADRATIC_CURVE_TO_NV); |
| 266 | appendControlCoords(v: &d->path.coord, c: o, pos); |
| 267 | appendCoords(v: &d->path.coord, c: o, pos: &pos); |
| 268 | } else if (QQuickPathCubic *o = qobject_cast<QQuickPathCubic *>(object: e)) { |
| 269 | d->path.cmd.append(GL_CUBIC_CURVE_TO_NV); |
| 270 | appendControl1Coords(v: &d->path.coord, c: o, pos); |
| 271 | appendControl2Coords(v: &d->path.coord, c: o, pos); |
| 272 | appendCoords(v: &d->path.coord, c: o, pos: &pos); |
| 273 | } else if (QQuickPathArc *o = qobject_cast<QQuickPathArc *>(object: e)) { |
| 274 | const bool sweepFlag = o->direction() == QQuickPathArc::Clockwise; // maps to CCW, not a typo |
| 275 | GLenum cmd; |
| 276 | if (o->useLargeArc()) |
| 277 | cmd = sweepFlag ? GL_LARGE_CCW_ARC_TO_NV : GL_LARGE_CW_ARC_TO_NV; |
| 278 | else |
| 279 | cmd = sweepFlag ? GL_SMALL_CCW_ARC_TO_NV : GL_SMALL_CW_ARC_TO_NV; |
| 280 | d->path.cmd.append(t: cmd); |
| 281 | d->path.coord.append(t: o->radiusX()); |
| 282 | d->path.coord.append(t: o->radiusY()); |
| 283 | d->path.coord.append(t: o->xAxisRotation()); |
| 284 | appendCoords(v: &d->path.coord, c: o, pos: &pos); |
| 285 | } else if (QQuickPathSvg *o = qobject_cast<QQuickPathSvg *>(object: e)) { |
| 286 | // PathSvg cannot be combined with other elements. But take at |
| 287 | // least startX and startY into account. |
| 288 | if (d->path.str.isEmpty()) |
| 289 | d->path.str = QString(QStringLiteral("M %1 %2 " )).arg(a: pos.x()).arg(a: pos.y()).toUtf8(); |
| 290 | d->path.str.append(a: o->path().toUtf8()); |
| 291 | } else if (QQuickPathAngleArc *o = qobject_cast<QQuickPathAngleArc *>(object: e)) { |
| 292 | QRectF rect(o->centerX() - o->radiusX(), o->centerY() - o->radiusY(), o->radiusX() * 2, o->radiusY() * 2); |
| 293 | QPointF startPoint; |
| 294 | QPointF endPoint; |
| 295 | qt_find_ellipse_coords(r: rect, angle: o->startAngle(), length: -o->sweepAngle(), startPoint: &startPoint, endPoint: &endPoint); |
| 296 | |
| 297 | // get to our starting position |
| 298 | if (o->moveToStart()) |
| 299 | d->path.cmd.append(GL_MOVE_TO_NV); |
| 300 | else |
| 301 | d->path.cmd.append(GL_LINE_TO_NV); // ### should we check if startPoint == pos? |
| 302 | d->path.coord.append(t: startPoint.x()); |
| 303 | d->path.coord.append(t: startPoint.y()); |
| 304 | |
| 305 | const bool sweepFlag = o->sweepAngle() > 0; // maps to CCW, not a typo |
| 306 | d->path.cmd.append(t: qAbs(t: o->sweepAngle()) > 180.0 |
| 307 | ? (sweepFlag ? GL_LARGE_CCW_ARC_TO_NV : GL_LARGE_CW_ARC_TO_NV) |
| 308 | : (sweepFlag ? GL_SMALL_CCW_ARC_TO_NV : GL_SMALL_CW_ARC_TO_NV)); |
| 309 | d->path.coord.append(t: o->radiusX()); |
| 310 | d->path.coord.append(t: o->radiusY()); |
| 311 | d->path.coord.append(t: 0); // xAxisRotation |
| 312 | d->path.coord.append(t: endPoint.x()); |
| 313 | d->path.coord.append(t: endPoint.y()); |
| 314 | pos = endPoint; |
| 315 | } else { |
| 316 | qWarning() << "Shape/NVPR: unsupported Path element" << e; |
| 317 | } |
| 318 | } |
| 319 | |
| 320 | // For compatibility with QTriangulatingStroker. SVG and others would not |
| 321 | // implicitly close the path when end_pos == start_pos (start_pos being the |
| 322 | // last moveTo pos); that would still need an explicit 'z' or similar. We |
| 323 | // don't have an explicit close command, so just fake a close when the |
| 324 | // positions match. |
| 325 | if (pos == startPos) |
| 326 | d->path.cmd.append(GL_CLOSE_PATH_NV); |
| 327 | } |
| 328 | |
| 329 | static inline QVector4D qsg_premultiply(const QColor &c, float globalOpacity) |
| 330 | { |
| 331 | const float o = c.alphaF() * globalOpacity; |
| 332 | return QVector4D(c.redF() * o, c.greenF() * o, c.blueF() * o, o); |
| 333 | } |
| 334 | |
| 335 | void QQuickShapeNvprRenderer::updateNode() |
| 336 | { |
| 337 | // Called on the render thread with gui blocked -> update the node with its |
| 338 | // own copy of all relevant data. |
| 339 | |
| 340 | if (!m_accDirty) |
| 341 | return; |
| 342 | |
| 343 | const int count = m_sp.count(); |
| 344 | const bool listChanged = m_accDirty & DirtyList; |
| 345 | if (listChanged) |
| 346 | m_node->m_sp.resize(asize: count); |
| 347 | |
| 348 | for (int i = 0; i < count; ++i) { |
| 349 | ShapePathGuiData &src(m_sp[i]); |
| 350 | QQuickShapeNvprRenderNode::ShapePathRenderData &dst(m_node->m_sp[i]); |
| 351 | |
| 352 | int dirty = src.dirty; |
| 353 | src.dirty = 0; |
| 354 | if (listChanged) |
| 355 | dirty |= DirtyPath | DirtyStyle | DirtyFillRule | DirtyDash | DirtyFillGradient; |
| 356 | |
| 357 | // updateNode() can be called several times with different dirty |
| 358 | // states before render() gets invoked. So accumulate. |
| 359 | dst.dirty |= dirty; |
| 360 | |
| 361 | if (dirty & DirtyPath) |
| 362 | dst.source = src.path; |
| 363 | |
| 364 | if (dirty & DirtyStyle) { |
| 365 | dst.strokeWidth = src.strokeWidth; |
| 366 | dst.strokeColor = qsg_premultiply(c: src.strokeColor, globalOpacity: 1.0f); |
| 367 | dst.fillColor = qsg_premultiply(c: src.fillColor, globalOpacity: 1.0f); |
| 368 | switch (src.joinStyle) { |
| 369 | case QQuickShapePath::MiterJoin: |
| 370 | dst.joinStyle = GL_MITER_TRUNCATE_NV; |
| 371 | break; |
| 372 | case QQuickShapePath::BevelJoin: |
| 373 | dst.joinStyle = GL_BEVEL_NV; |
| 374 | break; |
| 375 | case QQuickShapePath::RoundJoin: |
| 376 | dst.joinStyle = GL_ROUND_NV; |
| 377 | break; |
| 378 | default: |
| 379 | Q_UNREACHABLE(); |
| 380 | } |
| 381 | dst.miterLimit = src.miterLimit; |
| 382 | switch (src.capStyle) { |
| 383 | case QQuickShapePath::FlatCap: |
| 384 | dst.capStyle = GL_FLAT; |
| 385 | break; |
| 386 | case QQuickShapePath::SquareCap: |
| 387 | dst.capStyle = GL_SQUARE_NV; |
| 388 | break; |
| 389 | case QQuickShapePath::RoundCap: |
| 390 | dst.capStyle = GL_ROUND_NV; |
| 391 | break; |
| 392 | default: |
| 393 | Q_UNREACHABLE(); |
| 394 | } |
| 395 | } |
| 396 | |
| 397 | if (dirty & DirtyFillRule) { |
| 398 | switch (src.fillRule) { |
| 399 | case QQuickShapePath::OddEvenFill: |
| 400 | dst.fillRule = GL_INVERT; |
| 401 | break; |
| 402 | case QQuickShapePath::WindingFill: |
| 403 | dst.fillRule = GL_COUNT_UP_NV; |
| 404 | break; |
| 405 | default: |
| 406 | Q_UNREACHABLE(); |
| 407 | } |
| 408 | } |
| 409 | |
| 410 | if (dirty & DirtyDash) { |
| 411 | // Multiply by strokeWidth because the Shape API follows QPen |
| 412 | // meaning the input dash pattern and dash offset here are in width units. |
| 413 | dst.dashOffset = src.dashOffset * src.strokeWidth; |
| 414 | if (src.dashActive) { |
| 415 | if (src.dashPattern.isEmpty()) { |
| 416 | // default values for DashLine as defined in qpen.cpp |
| 417 | dst.dashPattern.resize(asize: 2); |
| 418 | dst.dashPattern[0] = 4 * src.strokeWidth; // dash |
| 419 | dst.dashPattern[1] = 2 * src.strokeWidth; // space |
| 420 | } else { |
| 421 | dst.dashPattern.resize(asize: src.dashPattern.count()); |
| 422 | for (int i = 0; i < src.dashPattern.count(); ++i) |
| 423 | dst.dashPattern[i] = GLfloat(src.dashPattern[i]) * src.strokeWidth; |
| 424 | |
| 425 | // QPen expects a dash pattern of even length and so should we |
| 426 | if (src.dashPattern.count() % 2 != 0) { |
| 427 | qWarning(msg: "QQuickShapeNvprRenderNode: dash pattern not of even length" ); |
| 428 | dst.dashPattern << src.strokeWidth; |
| 429 | } |
| 430 | } |
| 431 | } else { |
| 432 | dst.dashPattern.clear(); |
| 433 | } |
| 434 | } |
| 435 | |
| 436 | if (dirty & DirtyFillGradient) { |
| 437 | dst.fillGradientActive = src.fillGradientActive; |
| 438 | if (src.fillGradientActive) |
| 439 | dst.fillGradient = src.fillGradient; |
| 440 | } |
| 441 | } |
| 442 | |
| 443 | m_node->markDirty(bits: QSGNode::DirtyMaterial); |
| 444 | m_accDirty = 0; |
| 445 | } |
| 446 | |
| 447 | bool QQuickShapeNvprRenderNode::nvprInited = false; |
| 448 | QQuickNvprFunctions QQuickShapeNvprRenderNode::nvpr; |
| 449 | QQuickNvprMaterialManager QQuickShapeNvprRenderNode::mtlmgr; |
| 450 | |
| 451 | QQuickShapeNvprRenderNode::~QQuickShapeNvprRenderNode() |
| 452 | { |
| 453 | releaseResources(); |
| 454 | } |
| 455 | |
| 456 | void QQuickShapeNvprRenderNode::releaseResources() |
| 457 | { |
| 458 | for (ShapePathRenderData &d : m_sp) { |
| 459 | if (d.path) { |
| 460 | nvpr.deletePaths(d.path, 1); |
| 461 | d.path = 0; |
| 462 | } |
| 463 | if (d.fallbackFbo) { |
| 464 | delete d.fallbackFbo; |
| 465 | d.fallbackFbo = nullptr; |
| 466 | } |
| 467 | } |
| 468 | |
| 469 | m_fallbackBlitter.destroy(); |
| 470 | } |
| 471 | |
| 472 | void QQuickNvprMaterialManager::create(QQuickNvprFunctions *nvpr) |
| 473 | { |
| 474 | m_nvpr = nvpr; |
| 475 | } |
| 476 | |
| 477 | void QQuickNvprMaterialManager::releaseResources() |
| 478 | { |
| 479 | QOpenGLExtraFunctions *f = QOpenGLContext::currentContext()->extraFunctions(); |
| 480 | for (MaterialDesc &mtl : m_materials) { |
| 481 | if (mtl.ppl) { |
| 482 | f->glDeleteProgramPipelines(n: 1, pipelines: &mtl.ppl); |
| 483 | mtl = MaterialDesc(); |
| 484 | } |
| 485 | } |
| 486 | } |
| 487 | |
| 488 | QQuickNvprMaterialManager::MaterialDesc *QQuickNvprMaterialManager::activateMaterial(Material m) |
| 489 | { |
| 490 | QOpenGLExtraFunctions *f = QOpenGLContext::currentContext()->extraFunctions(); |
| 491 | MaterialDesc &mtl(m_materials[m]); |
| 492 | |
| 493 | if (!mtl.ppl) { |
| 494 | if (m == MatSolid) { |
| 495 | static const char *fragSrc = |
| 496 | "#version 310 es\n" |
| 497 | "precision highp float;\n" |
| 498 | "out vec4 fragColor;\n" |
| 499 | "uniform vec4 color;\n" |
| 500 | "uniform float opacity;\n" |
| 501 | "void main() {\n" |
| 502 | " fragColor = color * opacity;\n" |
| 503 | "}\n" ; |
| 504 | if (!m_nvpr->createFragmentOnlyPipeline(fragmentShaderSource: fragSrc, pipeline: &mtl.ppl, program: &mtl.prg)) { |
| 505 | qWarning(msg: "NVPR: Failed to create shader pipeline for solid fill" ); |
| 506 | return nullptr; |
| 507 | } |
| 508 | Q_ASSERT(mtl.ppl && mtl.prg); |
| 509 | mtl.uniLoc[0] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "color" ); |
| 510 | Q_ASSERT(mtl.uniLoc[0] >= 0); |
| 511 | mtl.uniLoc[1] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "opacity" ); |
| 512 | Q_ASSERT(mtl.uniLoc[1] >= 0); |
| 513 | } else if (m == MatLinearGradient) { |
| 514 | static const char *fragSrc = |
| 515 | "#version 310 es\n" |
| 516 | "precision highp float;\n" |
| 517 | "layout(location = 0) in vec2 uv;" |
| 518 | "uniform float opacity;\n" |
| 519 | "uniform sampler2D gradTab;\n" |
| 520 | "uniform vec2 gradStart;\n" |
| 521 | "uniform vec2 gradEnd;\n" |
| 522 | "out vec4 fragColor;\n" |
| 523 | "void main() {\n" |
| 524 | " vec2 gradVec = gradEnd - gradStart;\n" |
| 525 | " float gradTabIndex = dot(gradVec, uv - gradStart) / (gradVec.x * gradVec.x + gradVec.y * gradVec.y);\n" |
| 526 | " fragColor = texture(gradTab, vec2(gradTabIndex, 0.5)) * opacity;\n" |
| 527 | "}\n" ; |
| 528 | if (!m_nvpr->createFragmentOnlyPipeline(fragmentShaderSource: fragSrc, pipeline: &mtl.ppl, program: &mtl.prg)) { |
| 529 | qWarning(msg: "NVPR: Failed to create shader pipeline for linear gradient" ); |
| 530 | return nullptr; |
| 531 | } |
| 532 | Q_ASSERT(mtl.ppl && mtl.prg); |
| 533 | mtl.uniLoc[1] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "opacity" ); |
| 534 | Q_ASSERT(mtl.uniLoc[1] >= 0); |
| 535 | mtl.uniLoc[2] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "gradStart" ); |
| 536 | Q_ASSERT(mtl.uniLoc[2] >= 0); |
| 537 | mtl.uniLoc[3] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "gradEnd" ); |
| 538 | Q_ASSERT(mtl.uniLoc[3] >= 0); |
| 539 | } else if (m == MatRadialGradient) { |
| 540 | static const char *fragSrc = |
| 541 | "#version 310 es\n" |
| 542 | "precision highp float;\n" |
| 543 | "uniform sampler2D gradTab;\n" |
| 544 | "uniform float opacity;\n" |
| 545 | "uniform vec2 focalToCenter;\n" |
| 546 | "uniform float centerRadius;\n" |
| 547 | "uniform float focalRadius;\n" |
| 548 | "uniform vec2 translationPoint;\n" |
| 549 | "layout(location = 0) in vec2 uv;\n" |
| 550 | "out vec4 fragColor;\n" |
| 551 | "void main() {\n" |
| 552 | " vec2 coord = uv - translationPoint;\n" |
| 553 | " float rd = centerRadius - focalRadius;\n" |
| 554 | " float b = 2.0 * (rd * focalRadius + dot(coord, focalToCenter));\n" |
| 555 | " float fmp2_m_radius2 = -focalToCenter.x * focalToCenter.x - focalToCenter.y * focalToCenter.y + rd * rd;\n" |
| 556 | " float inverse_2_fmp2_m_radius2 = 1.0 / (2.0 * fmp2_m_radius2);\n" |
| 557 | " float det = b * b - 4.0 * fmp2_m_radius2 * ((focalRadius * focalRadius) - dot(coord, coord));\n" |
| 558 | " vec4 result = vec4(0.0);\n" |
| 559 | " if (det >= 0.0) {\n" |
| 560 | " float detSqrt = sqrt(det);\n" |
| 561 | " float w = max((-b - detSqrt) * inverse_2_fmp2_m_radius2, (-b + detSqrt) * inverse_2_fmp2_m_radius2);\n" |
| 562 | " if (focalRadius + w * (centerRadius - focalRadius) >= 0.0)\n" |
| 563 | " result = texture(gradTab, vec2(w, 0.5)) * opacity;\n" |
| 564 | " }\n" |
| 565 | " fragColor = result;\n" |
| 566 | "}\n" ; |
| 567 | if (!m_nvpr->createFragmentOnlyPipeline(fragmentShaderSource: fragSrc, pipeline: &mtl.ppl, program: &mtl.prg)) { |
| 568 | qWarning(msg: "NVPR: Failed to create shader pipeline for radial gradient" ); |
| 569 | return nullptr; |
| 570 | } |
| 571 | Q_ASSERT(mtl.ppl && mtl.prg); |
| 572 | mtl.uniLoc[1] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "opacity" ); |
| 573 | Q_ASSERT(mtl.uniLoc[1] >= 0); |
| 574 | mtl.uniLoc[2] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "focalToCenter" ); |
| 575 | Q_ASSERT(mtl.uniLoc[2] >= 0); |
| 576 | mtl.uniLoc[3] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "centerRadius" ); |
| 577 | Q_ASSERT(mtl.uniLoc[3] >= 0); |
| 578 | mtl.uniLoc[4] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "focalRadius" ); |
| 579 | Q_ASSERT(mtl.uniLoc[4] >= 0); |
| 580 | mtl.uniLoc[5] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "translationPoint" ); |
| 581 | Q_ASSERT(mtl.uniLoc[5] >= 0); |
| 582 | } else if (m == MatConicalGradient) { |
| 583 | static const char *fragSrc = |
| 584 | "#version 310 es\n" |
| 585 | "precision highp float;\n" |
| 586 | "#define INVERSE_2PI 0.1591549430918953358\n" |
| 587 | "uniform sampler2D gradTab;\n" |
| 588 | "uniform float opacity;\n" |
| 589 | "uniform float angle;\n" |
| 590 | "uniform vec2 translationPoint;\n" |
| 591 | "layout(location = 0) in vec2 uv;\n" |
| 592 | "out vec4 fragColor;\n" |
| 593 | "void main() {\n" |
| 594 | " vec2 coord = uv - translationPoint;\n" |
| 595 | " float t;\n" |
| 596 | " if (abs(coord.y) == abs(coord.x))\n" |
| 597 | " t = (atan(-coord.y + 0.002, coord.x) + angle) * INVERSE_2PI;\n" |
| 598 | " else\n" |
| 599 | " t = (atan(-coord.y, coord.x) + angle) * INVERSE_2PI;\n" |
| 600 | " fragColor = texture(gradTab, vec2(t - floor(t), 0.5)) * opacity;\n" |
| 601 | "}\n" ; |
| 602 | if (!m_nvpr->createFragmentOnlyPipeline(fragmentShaderSource: fragSrc, pipeline: &mtl.ppl, program: &mtl.prg)) { |
| 603 | qWarning(msg: "NVPR: Failed to create shader pipeline for conical gradient" ); |
| 604 | return nullptr; |
| 605 | } |
| 606 | Q_ASSERT(mtl.ppl && mtl.prg); |
| 607 | mtl.uniLoc[1] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "opacity" ); |
| 608 | Q_ASSERT(mtl.uniLoc[1] >= 0); |
| 609 | mtl.uniLoc[2] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "angle" ); |
| 610 | Q_ASSERT(mtl.uniLoc[2] >= 0); |
| 611 | mtl.uniLoc[3] = f->glGetProgramResourceLocation(program: mtl.prg, GL_UNIFORM, name: "translationPoint" ); |
| 612 | Q_ASSERT(mtl.uniLoc[3] >= 0); |
| 613 | } else { |
| 614 | Q_UNREACHABLE(); |
| 615 | } |
| 616 | } |
| 617 | |
| 618 | f->glBindProgramPipeline(pipeline: mtl.ppl); |
| 619 | |
| 620 | return &mtl; |
| 621 | } |
| 622 | |
| 623 | void QQuickShapeNvprRenderNode::updatePath(ShapePathRenderData *d) |
| 624 | { |
| 625 | if (d->dirty & QQuickShapeNvprRenderer::DirtyPath) { |
| 626 | if (!d->path) { |
| 627 | d->path = nvpr.genPaths(1); |
| 628 | Q_ASSERT(d->path != 0); |
| 629 | } |
| 630 | if (d->source.str.isEmpty()) { |
| 631 | nvpr.pathCommands(d->path, d->source.cmd.count(), d->source.cmd.constData(), |
| 632 | d->source.coord.count(), GL_FLOAT, d->source.coord.constData()); |
| 633 | } else { |
| 634 | nvpr.pathString(d->path, GL_PATH_FORMAT_SVG_NV, d->source.str.count(), d->source.str.constData()); |
| 635 | } |
| 636 | } |
| 637 | |
| 638 | if (d->dirty & QQuickShapeNvprRenderer::DirtyStyle) { |
| 639 | nvpr.pathParameterf(d->path, GL_PATH_STROKE_WIDTH_NV, d->strokeWidth); |
| 640 | nvpr.pathParameteri(d->path, GL_PATH_JOIN_STYLE_NV, d->joinStyle); |
| 641 | nvpr.pathParameteri(d->path, GL_PATH_MITER_LIMIT_NV, d->miterLimit); |
| 642 | nvpr.pathParameteri(d->path, GL_PATH_END_CAPS_NV, d->capStyle); |
| 643 | nvpr.pathParameteri(d->path, GL_PATH_DASH_CAPS_NV, d->capStyle); |
| 644 | } |
| 645 | |
| 646 | if (d->dirty & QQuickShapeNvprRenderer::DirtyDash) { |
| 647 | nvpr.pathParameterf(d->path, GL_PATH_DASH_OFFSET_NV, d->dashOffset); |
| 648 | // count == 0 -> no dash |
| 649 | nvpr.pathDashArray(d->path, d->dashPattern.count(), d->dashPattern.constData()); |
| 650 | } |
| 651 | |
| 652 | if (d->dirty) |
| 653 | d->fallbackValid = false; |
| 654 | } |
| 655 | |
| 656 | void QQuickShapeNvprRenderNode::renderStroke(ShapePathRenderData *d, int strokeStencilValue, int writeMask) |
| 657 | { |
| 658 | QQuickNvprMaterialManager::MaterialDesc *mtl = mtlmgr.activateMaterial(m: QQuickNvprMaterialManager::MatSolid); |
| 659 | f->glProgramUniform4f(program: mtl->prg, location: mtl->uniLoc[0], |
| 660 | v0: d->strokeColor.x(), v1: d->strokeColor.y(), v2: d->strokeColor.z(), v3: d->strokeColor.w()); |
| 661 | f->glProgramUniform1f(program: mtl->prg, location: mtl->uniLoc[1], v0: inheritedOpacity()); |
| 662 | |
| 663 | nvpr.stencilThenCoverStrokePath(d->path, strokeStencilValue, writeMask, GL_CONVEX_HULL_NV); |
| 664 | } |
| 665 | |
| 666 | void QQuickShapeNvprRenderNode::renderFill(ShapePathRenderData *d) |
| 667 | { |
| 668 | QQuickNvprMaterialManager::MaterialDesc *mtl = nullptr; |
| 669 | if (d->fillGradientActive) { |
| 670 | QQuickShapeGradient::SpreadMode spread = d->fillGradient.spread; |
| 671 | if (d->fillGradientActive == QQuickAbstractPathRenderer::LinearGradient) { |
| 672 | mtl = mtlmgr.activateMaterial(m: QQuickNvprMaterialManager::MatLinearGradient); |
| 673 | // uv = vec2(coeff[0] * x + coeff[1] * y + coeff[2], coeff[3] * x + coeff[4] * y + coeff[5]) |
| 674 | // where x and y are in path coordinate space, which is just what |
| 675 | // we need since the gradient's start and stop are in that space too. |
| 676 | GLfloat coeff[6] = { 1, 0, 0, |
| 677 | 0, 1, 0 }; |
| 678 | nvpr.programPathFragmentInputGen(mtl->prg, 0, GL_OBJECT_LINEAR_NV, 2, coeff); |
| 679 | f->glProgramUniform2f(program: mtl->prg, location: mtl->uniLoc[2], v0: d->fillGradient.a.x(), v1: d->fillGradient.a.y()); |
| 680 | f->glProgramUniform2f(program: mtl->prg, location: mtl->uniLoc[3], v0: d->fillGradient.b.x(), v1: d->fillGradient.b.y()); |
| 681 | } else if (d->fillGradientActive == QQuickAbstractPathRenderer::RadialGradient) { |
| 682 | mtl = mtlmgr.activateMaterial(m: QQuickNvprMaterialManager::MatRadialGradient); |
| 683 | // simply drive uv (location 0) with x and y, just like for the linear gradient |
| 684 | GLfloat coeff[6] = { 1, 0, 0, |
| 685 | 0, 1, 0 }; |
| 686 | nvpr.programPathFragmentInputGen(mtl->prg, 0, GL_OBJECT_LINEAR_NV, 2, coeff); |
| 687 | |
| 688 | const QPointF centerPoint = d->fillGradient.a; |
| 689 | const QPointF focalPoint = d->fillGradient.b; |
| 690 | const QPointF focalToCenter = centerPoint - focalPoint; |
| 691 | const GLfloat centerRadius = d->fillGradient.v0; |
| 692 | const GLfloat focalRadius = d->fillGradient.v1; |
| 693 | |
| 694 | f->glProgramUniform2f(program: mtl->prg, location: mtl->uniLoc[2], v0: focalToCenter.x(), v1: focalToCenter.y()); |
| 695 | f->glProgramUniform1f(program: mtl->prg, location: mtl->uniLoc[3], v0: centerRadius); |
| 696 | f->glProgramUniform1f(program: mtl->prg, location: mtl->uniLoc[4], v0: focalRadius); |
| 697 | f->glProgramUniform2f(program: mtl->prg, location: mtl->uniLoc[5], v0: focalPoint.x(), v1: focalPoint.y()); |
| 698 | } else if (d->fillGradientActive == QQuickAbstractPathRenderer::ConicalGradient) { |
| 699 | mtl = mtlmgr.activateMaterial(m: QQuickNvprMaterialManager::MatConicalGradient); |
| 700 | // same old |
| 701 | GLfloat coeff[6] = { 1, 0, 0, |
| 702 | 0, 1, 0 }; |
| 703 | nvpr.programPathFragmentInputGen(mtl->prg, 0, GL_OBJECT_LINEAR_NV, 2, coeff); |
| 704 | |
| 705 | const QPointF centerPoint = d->fillGradient.a; |
| 706 | const GLfloat angle = -qDegreesToRadians(degrees: d->fillGradient.v0); |
| 707 | |
| 708 | f->glProgramUniform1f(program: mtl->prg, location: mtl->uniLoc[2], v0: angle); |
| 709 | f->glProgramUniform2f(program: mtl->prg, location: mtl->uniLoc[3], v0: centerPoint.x(), v1: centerPoint.y()); |
| 710 | |
| 711 | spread = QQuickShapeGradient::RepeatSpread; |
| 712 | } else { |
| 713 | Q_UNREACHABLE(); |
| 714 | } |
| 715 | const QQuickShapeGradientCacheKey cacheKey(d->fillGradient.stops, spread); |
| 716 | QSGTexture *tx = QQuickShapeGradientOpenGLCache::currentCache()->get(grad: cacheKey); |
| 717 | tx->bind(); |
| 718 | } else { |
| 719 | mtl = mtlmgr.activateMaterial(m: QQuickNvprMaterialManager::MatSolid); |
| 720 | f->glProgramUniform4f(program: mtl->prg, location: mtl->uniLoc[0], |
| 721 | v0: d->fillColor.x(), v1: d->fillColor.y(), v2: d->fillColor.z(), v3: d->fillColor.w()); |
| 722 | } |
| 723 | f->glProgramUniform1f(program: mtl->prg, location: mtl->uniLoc[1], v0: inheritedOpacity()); |
| 724 | |
| 725 | const int writeMask = 0xFF; |
| 726 | nvpr.stencilThenCoverFillPath(d->path, d->fillRule, writeMask, GL_BOUNDING_BOX_NV); |
| 727 | } |
| 728 | |
| 729 | void QQuickShapeNvprRenderNode::renderOffscreenFill(ShapePathRenderData *d) |
| 730 | { |
| 731 | if (d->fallbackValid && d->fallbackFbo) |
| 732 | return; |
| 733 | |
| 734 | GLfloat bb[4]; |
| 735 | nvpr.getPathParameterfv(d->path, GL_PATH_STROKE_BOUNDING_BOX_NV, bb); |
| 736 | QSize sz = QSizeF(bb[2] - bb[0] + 1, bb[3] - bb[1] + 1).toSize(); |
| 737 | d->fallbackSize = QSize(qMax(a: 32, b: sz.width()), qMax(a: 32, b: sz.height())); |
| 738 | d->fallbackTopLeft = QPointF(bb[0], bb[1]); |
| 739 | |
| 740 | if (d->fallbackFbo && d->fallbackFbo->size() != d->fallbackSize) { |
| 741 | delete d->fallbackFbo; |
| 742 | d->fallbackFbo = nullptr; |
| 743 | } |
| 744 | if (!d->fallbackFbo) |
| 745 | d->fallbackFbo = new QOpenGLFramebufferObject(d->fallbackSize, QOpenGLFramebufferObject::CombinedDepthStencil); |
| 746 | if (!d->fallbackFbo->bind()) |
| 747 | return; |
| 748 | |
| 749 | GLint prevViewport[4]; |
| 750 | f->glGetIntegerv(GL_VIEWPORT, params: prevViewport); |
| 751 | |
| 752 | f->glViewport(x: 0, y: 0, width: d->fallbackSize.width(), height: d->fallbackSize.height()); |
| 753 | f->glDisable(GL_DEPTH_TEST); |
| 754 | f->glClearColor(red: 0, green: 0, blue: 0, alpha: 0); |
| 755 | f->glClearStencil(s: 0); |
| 756 | f->glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); |
| 757 | f->glStencilFunc(GL_NOTEQUAL, ref: 0, mask: 0xFF); |
| 758 | f->glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); |
| 759 | |
| 760 | QMatrix4x4 mv; |
| 761 | mv.translate(x: -d->fallbackTopLeft.x(), y: -d->fallbackTopLeft.y()); |
| 762 | nvpr.matrixLoadf(GL_PATH_MODELVIEW_NV, mv.constData()); |
| 763 | QMatrix4x4 proj; |
| 764 | proj.ortho(left: 0, right: d->fallbackSize.width(), bottom: d->fallbackSize.height(), top: 0, nearPlane: 1, farPlane: -1); |
| 765 | nvpr.matrixLoadf(GL_PATH_PROJECTION_NV, proj.constData()); |
| 766 | |
| 767 | renderFill(d); |
| 768 | |
| 769 | d->fallbackFbo->release(); |
| 770 | f->glEnable(GL_DEPTH_TEST); |
| 771 | f->glViewport(x: prevViewport[0], y: prevViewport[1], width: prevViewport[2], height: prevViewport[3]); |
| 772 | |
| 773 | d->fallbackValid = true; |
| 774 | } |
| 775 | |
| 776 | void QQuickShapeNvprRenderNode::setupStencilForCover(bool stencilClip, int sv) |
| 777 | { |
| 778 | if (!stencilClip) { |
| 779 | // Assume stencil buffer is cleared to 0 for each frame. |
| 780 | // Within the frame dppass=GL_ZERO for glStencilOp ensures stencil is reset and so no need to clear. |
| 781 | f->glStencilFunc(GL_NOTEQUAL, ref: 0, mask: 0xFF); |
| 782 | f->glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO); |
| 783 | } else { |
| 784 | f->glStencilFunc(GL_LESS, ref: sv, mask: 0xFF); // pass if (sv & 0xFF) < (stencil_value & 0xFF) |
| 785 | f->glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); // dppass: replace with the original value (clip's stencil ref value) |
| 786 | } |
| 787 | } |
| 788 | |
| 789 | void QQuickShapeNvprRenderNode::render(const RenderState *state) |
| 790 | { |
| 791 | f = QOpenGLContext::currentContext()->extraFunctions(); |
| 792 | |
| 793 | if (!nvprInited) { |
| 794 | if (!nvpr.create()) { |
| 795 | qWarning(msg: "NVPR init failed" ); |
| 796 | return; |
| 797 | } |
| 798 | mtlmgr.create(nvpr: &nvpr); |
| 799 | nvprInited = true; |
| 800 | } |
| 801 | |
| 802 | f->glUseProgram(program: 0); |
| 803 | f->glStencilMask(mask: ~0); |
| 804 | f->glEnable(GL_STENCIL_TEST); |
| 805 | |
| 806 | const bool stencilClip = state->stencilEnabled(); |
| 807 | // when true, the stencil buffer already has a clip path with a ref value of sv |
| 808 | const int sv = state->stencilValue(); |
| 809 | const bool hasScissor = state->scissorEnabled(); |
| 810 | |
| 811 | if (hasScissor) { |
| 812 | // scissor rect is already set, just enable scissoring |
| 813 | f->glEnable(GL_SCISSOR_TEST); |
| 814 | } |
| 815 | |
| 816 | // Depth test against the opaque batches rendered before. |
| 817 | f->glEnable(GL_DEPTH_TEST); |
| 818 | f->glDepthFunc(GL_LESS); |
| 819 | nvpr.pathCoverDepthFunc(GL_LESS); |
| 820 | nvpr.pathStencilDepthOffset(-0.05f, -1); |
| 821 | |
| 822 | bool reloadMatrices = true; |
| 823 | |
| 824 | for (ShapePathRenderData &d : m_sp) { |
| 825 | updatePath(d: &d); |
| 826 | |
| 827 | const bool hasFill = d.hasFill(); |
| 828 | const bool hasStroke = d.hasStroke(); |
| 829 | |
| 830 | if (hasFill && stencilClip) { |
| 831 | // Fall back to a texture when complex clipping is in use and we have |
| 832 | // to fill. Reconciling glStencilFillPath's and the scenegraph's clip |
| 833 | // stencil semantics has not succeeded so far... |
| 834 | if (hasScissor) |
| 835 | f->glDisable(GL_SCISSOR_TEST); |
| 836 | renderOffscreenFill(d: &d); |
| 837 | reloadMatrices = true; |
| 838 | if (hasScissor) |
| 839 | f->glEnable(GL_SCISSOR_TEST); |
| 840 | } |
| 841 | |
| 842 | if (reloadMatrices) { |
| 843 | reloadMatrices = false; |
| 844 | nvpr.matrixLoadf(GL_PATH_MODELVIEW_NV, matrix()->constData()); |
| 845 | nvpr.matrixLoadf(GL_PATH_PROJECTION_NV, state->projectionMatrix()->constData()); |
| 846 | } |
| 847 | |
| 848 | // Fill! |
| 849 | if (hasFill) { |
| 850 | if (!stencilClip) { |
| 851 | setupStencilForCover(stencilClip: false, sv: 0); |
| 852 | renderFill(d: &d); |
| 853 | } else { |
| 854 | if (!m_fallbackBlitter.isCreated()) |
| 855 | m_fallbackBlitter.create(); |
| 856 | f->glStencilFunc(GL_EQUAL, ref: sv, mask: 0xFF); |
| 857 | f->glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); |
| 858 | QMatrix4x4 mv = *matrix(); |
| 859 | mv.translate(x: d.fallbackTopLeft.x(), y: d.fallbackTopLeft.y()); |
| 860 | m_fallbackBlitter.texturedQuad(textureId: d.fallbackFbo->texture(), size: d.fallbackFbo->size(), |
| 861 | proj: *state->projectionMatrix(), modelview: mv, |
| 862 | opacity: inheritedOpacity()); |
| 863 | } |
| 864 | } |
| 865 | |
| 866 | // Stroke! |
| 867 | if (hasStroke) { |
| 868 | const int strokeStencilValue = 0x80; |
| 869 | const int writeMask = 0x80; |
| 870 | |
| 871 | setupStencilForCover(stencilClip, sv); |
| 872 | if (stencilClip) { |
| 873 | // for the stencil step (eff. read mask == 0xFF & ~writeMask) |
| 874 | nvpr.pathStencilFunc(GL_EQUAL, sv, 0xFF); |
| 875 | // With stencilCLip == true the read mask for the stencil test before the stencil step is 0x7F. |
| 876 | // This assumes the clip stencil value is <= 127. |
| 877 | if (sv >= strokeStencilValue) |
| 878 | qWarning(msg: "Shape/NVPR: stencil clip ref value %d too large; expect rendering errors" , sv); |
| 879 | } |
| 880 | |
| 881 | renderStroke(d: &d, strokeStencilValue, writeMask); |
| 882 | } |
| 883 | |
| 884 | if (stencilClip) |
| 885 | nvpr.pathStencilFunc(GL_ALWAYS, 0, ~0); |
| 886 | |
| 887 | d.dirty = 0; |
| 888 | } |
| 889 | |
| 890 | f->glBindProgramPipeline(pipeline: 0); |
| 891 | } |
| 892 | |
| 893 | QSGRenderNode::StateFlags QQuickShapeNvprRenderNode::changedStates() const |
| 894 | { |
| 895 | return BlendState | StencilState | DepthState | ScissorState; |
| 896 | } |
| 897 | |
| 898 | QSGRenderNode::RenderingFlags QQuickShapeNvprRenderNode::flags() const |
| 899 | { |
| 900 | return DepthAwareRendering; // avoid hitting the less optimal no-opaque-batch path in the renderer |
| 901 | } |
| 902 | |
| 903 | bool QQuickShapeNvprRenderNode::isSupported() |
| 904 | { |
| 905 | static const bool nvprDisabled = qEnvironmentVariableIntValue(varName: "QT_NO_NVPR" ) != 0; |
| 906 | return !nvprDisabled && QQuickNvprFunctions::isSupported(); |
| 907 | } |
| 908 | |
| 909 | bool QQuickNvprBlitter::create() |
| 910 | { |
| 911 | if (isCreated()) |
| 912 | destroy(); |
| 913 | |
| 914 | m_program = new QOpenGLShaderProgram; |
| 915 | if (QOpenGLContext::currentContext()->format().profile() == QSurfaceFormat::CoreProfile) { |
| 916 | m_program->addCacheableShaderFromSourceFile(type: QOpenGLShader::Vertex, QStringLiteral(":/qt-project.org/shapes/shaders/blit_core.vert" )); |
| 917 | m_program->addCacheableShaderFromSourceFile(type: QOpenGLShader::Fragment, QStringLiteral(":/qt-project.org/shapes/shaders/blit_core.frag" )); |
| 918 | } else { |
| 919 | m_program->addCacheableShaderFromSourceFile(type: QOpenGLShader::Vertex, QStringLiteral(":/qt-project.org/shapes/shaders/blit.vert" )); |
| 920 | m_program->addCacheableShaderFromSourceFile(type: QOpenGLShader::Fragment, QStringLiteral(":/qt-project.org/shapes/shaders/blit.frag" )); |
| 921 | } |
| 922 | m_program->bindAttributeLocation(name: "qt_Vertex" , location: 0); |
| 923 | m_program->bindAttributeLocation(name: "qt_MultiTexCoord0" , location: 1); |
| 924 | if (!m_program->link()) |
| 925 | return false; |
| 926 | |
| 927 | m_matrixLoc = m_program->uniformLocation(name: "qt_Matrix" ); |
| 928 | m_opacityLoc = m_program->uniformLocation(name: "qt_Opacity" ); |
| 929 | |
| 930 | m_buffer = new QOpenGLBuffer; |
| 931 | if (!m_buffer->create()) |
| 932 | return false; |
| 933 | m_buffer->bind(); |
| 934 | m_buffer->allocate(count: 4 * sizeof(GLfloat) * 6); |
| 935 | m_buffer->release(); |
| 936 | |
| 937 | return true; |
| 938 | } |
| 939 | |
| 940 | void QQuickNvprBlitter::destroy() |
| 941 | { |
| 942 | if (m_program) { |
| 943 | delete m_program; |
| 944 | m_program = nullptr; |
| 945 | } |
| 946 | if (m_buffer) { |
| 947 | delete m_buffer; |
| 948 | m_buffer = nullptr; |
| 949 | } |
| 950 | } |
| 951 | |
| 952 | void QQuickNvprBlitter::texturedQuad(GLuint textureId, const QSize &size, |
| 953 | const QMatrix4x4 &proj, const QMatrix4x4 &modelview, |
| 954 | float opacity) |
| 955 | { |
| 956 | QOpenGLExtraFunctions *f = QOpenGLContext::currentContext()->extraFunctions(); |
| 957 | |
| 958 | m_program->bind(); |
| 959 | |
| 960 | QMatrix4x4 m = proj * modelview; |
| 961 | m_program->setUniformValue(location: m_matrixLoc, value: m); |
| 962 | m_program->setUniformValue(location: m_opacityLoc, value: opacity); |
| 963 | |
| 964 | m_buffer->bind(); |
| 965 | |
| 966 | if (size != m_prevSize) { |
| 967 | m_prevSize = size; |
| 968 | |
| 969 | QPointF p0(size.width() - 1, size.height() - 1); |
| 970 | QPointF p1(0, 0); |
| 971 | QPointF p2(0, size.height() - 1); |
| 972 | QPointF p3(size.width() - 1, 0); |
| 973 | |
| 974 | GLfloat vertices[6 * 4] = { |
| 975 | GLfloat(p0.x()), GLfloat(p0.y()), 1, 0, |
| 976 | GLfloat(p1.x()), GLfloat(p1.y()), 0, 1, |
| 977 | GLfloat(p2.x()), GLfloat(p2.y()), 0, 0, |
| 978 | |
| 979 | GLfloat(p0.x()), GLfloat(p0.y()), 1, 0, |
| 980 | GLfloat(p3.x()), GLfloat(p3.y()), 1, 1, |
| 981 | GLfloat(p1.x()), GLfloat(p1.y()), 0, 1, |
| 982 | }; |
| 983 | |
| 984 | m_buffer->write(offset: 0, data: vertices, count: sizeof(vertices)); |
| 985 | } |
| 986 | |
| 987 | m_program->enableAttributeArray(location: 0); |
| 988 | m_program->enableAttributeArray(location: 1); |
| 989 | f->glVertexAttribPointer(indx: 0, size: 2, GL_FLOAT, GL_FALSE, stride: 4 * sizeof(GLfloat), ptr: nullptr); |
| 990 | f->glVertexAttribPointer(indx: 1, size: 2, GL_FLOAT, GL_FALSE, stride: 4 * sizeof(GLfloat), ptr: (const void *) (2 * sizeof(GLfloat))); |
| 991 | |
| 992 | f->glBindTexture(GL_TEXTURE_2D, texture: textureId); |
| 993 | |
| 994 | f->glDrawArrays(GL_TRIANGLES, first: 0, count: 6); |
| 995 | |
| 996 | f->glBindTexture(GL_TEXTURE_2D, texture: 0); |
| 997 | m_buffer->release(); |
| 998 | m_program->release(); |
| 999 | } |
| 1000 | |
| 1001 | QT_END_NAMESPACE |
| 1002 | |