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 | |