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 "qquickshadereffectsource_p.h" |
41 | |
42 | #include "qquickitem_p.h" |
43 | #include "qquickwindow_p.h" |
44 | #include <private/qsgadaptationlayer_p.h> |
45 | #include <QtQuick/private/qsgrenderer_p.h> |
46 | #include <qsgsimplerectnode.h> |
47 | |
48 | #include "qmath.h" |
49 | #include <QtQuick/private/qsgtexture_p.h> |
50 | #include <QtCore/QRunnable> |
51 | |
52 | QT_BEGIN_NAMESPACE |
53 | |
54 | class QQuickShaderEffectSourceTextureProvider : public QSGTextureProvider |
55 | { |
56 | Q_OBJECT |
57 | public: |
58 | QQuickShaderEffectSourceTextureProvider() |
59 | : sourceTexture(nullptr) |
60 | , mipmapFiltering(QSGTexture::None) |
61 | , filtering(QSGTexture::Nearest) |
62 | , horizontalWrap(QSGTexture::ClampToEdge) |
63 | , verticalWrap(QSGTexture::ClampToEdge) |
64 | { |
65 | } |
66 | |
67 | QSGTexture *texture() const override { |
68 | sourceTexture->setMipmapFiltering(mipmapFiltering); |
69 | sourceTexture->setFiltering(filtering); |
70 | sourceTexture->setHorizontalWrapMode(horizontalWrap); |
71 | sourceTexture->setVerticalWrapMode(verticalWrap); |
72 | return sourceTexture; |
73 | } |
74 | |
75 | QSGLayer *sourceTexture; |
76 | |
77 | QSGTexture::Filtering mipmapFiltering; |
78 | QSGTexture::Filtering filtering; |
79 | QSGTexture::WrapMode horizontalWrap; |
80 | QSGTexture::WrapMode verticalWrap; |
81 | }; |
82 | |
83 | class QQuickShaderEffectSourceCleanup : public QRunnable |
84 | { |
85 | public: |
86 | QQuickShaderEffectSourceCleanup(QSGLayer *t, QQuickShaderEffectSourceTextureProvider *p) |
87 | : texture(t) |
88 | , provider(p) |
89 | {} |
90 | void run() override { |
91 | delete texture; |
92 | delete provider; |
93 | } |
94 | QSGLayer *texture; |
95 | QQuickShaderEffectSourceTextureProvider *provider; |
96 | }; |
97 | |
98 | /*! |
99 | \qmltype ShaderEffectSource |
100 | \instantiates QQuickShaderEffectSource |
101 | \inqmlmodule QtQuick |
102 | \since 5.0 |
103 | \inherits Item |
104 | \ingroup qtquick-effects |
105 | \brief Renders a \l {Qt Quick} item into a texture and displays it. |
106 | |
107 | The ShaderEffectSource type renders \l sourceItem into a texture and |
108 | displays it in the scene. \l sourceItem is drawn into the texture as though |
109 | it was a fully opaque root item. Thus \l sourceItem itself can be |
110 | invisible, but still appear in the texture. |
111 | |
112 | ShaderEffectSource can be used as: |
113 | \list |
114 | \li a texture source in a \l ShaderEffect. |
115 | This allows you to apply custom shader effects to any \l {Qt Quick} item. |
116 | \li a cache for a complex item. |
117 | The complex item can be rendered once into the texture, which can |
118 | then be animated freely without the need to render the complex item |
119 | again every frame. |
120 | \li an opacity layer. |
121 | ShaderEffectSource allows you to apply an opacity to items as a group |
122 | rather than each item individually. |
123 | \endlist |
124 | |
125 | \table |
126 | \row |
127 | \li \image declarative-shadereffectsource.png |
128 | \li \qml |
129 | import QtQuick 2.0 |
130 | |
131 | Rectangle { |
132 | width: 200 |
133 | height: 100 |
134 | gradient: Gradient { |
135 | GradientStop { position: 0; color: "white" } |
136 | GradientStop { position: 1; color: "black" } |
137 | } |
138 | Row { |
139 | opacity: 0.5 |
140 | Item { |
141 | id: foo |
142 | width: 100; height: 100 |
143 | Rectangle { x: 5; y: 5; width: 60; height: 60; color: "red" } |
144 | Rectangle { x: 20; y: 20; width: 60; height: 60; color: "orange" } |
145 | Rectangle { x: 35; y: 35; width: 60; height: 60; color: "yellow" } |
146 | } |
147 | ShaderEffectSource { |
148 | width: 100; height: 100 |
149 | sourceItem: foo |
150 | } |
151 | } |
152 | } |
153 | \endqml |
154 | \endtable |
155 | |
156 | The ShaderEffectSource type does not redirect any mouse or keyboard |
157 | input to \l sourceItem. If you hide the \l sourceItem by setting |
158 | \l{Item::visible}{visible} to false or \l{Item::opacity}{opacity} to zero, |
159 | it will no longer react to input. In cases where the ShaderEffectSource is |
160 | meant to replace the \l sourceItem, you typically want to hide the |
161 | \l sourceItem while still handling input. For this, you can use |
162 | the \l hideSource property. |
163 | |
164 | \note The ShaderEffectSource relies on FBO multisampling support |
165 | to antialias edges. If the underlying hardware does not support this, |
166 | which is the case for most embedded graphics chips, edges rendered |
167 | inside a ShaderEffectSource will not be antialiased. One way to remedy |
168 | this is to double the size of the effect source and render it with |
169 | \c {smooth: true} (this is the default value of smooth). |
170 | This will be equivalent to 4x multisampling, at the cost of lower performance |
171 | and higher memory use. |
172 | |
173 | \warning In most cases, using a ShaderEffectSource will decrease |
174 | performance, and in all cases, it will increase video memory usage. |
175 | Rendering through a ShaderEffectSource might also lead to lower quality |
176 | since some OpenGL implementations support multisampled backbuffer, |
177 | but not multisampled framebuffer objects. |
178 | */ |
179 | |
180 | QQuickShaderEffectSource::QQuickShaderEffectSource(QQuickItem *parent) |
181 | : QQuickItem(parent) |
182 | , m_provider(nullptr) |
183 | , m_texture(nullptr) |
184 | , m_wrapMode(ClampToEdge) |
185 | , m_sourceItem(nullptr) |
186 | , m_textureSize(0, 0) |
187 | , m_format(RGBA) |
188 | , m_samples(0) |
189 | , m_live(true) |
190 | , m_hideSource(false) |
191 | , m_mipmap(false) |
192 | , m_recursive(false) |
193 | , m_grab(true) |
194 | , m_textureMirroring(MirrorVertically) |
195 | { |
196 | setFlag(flag: ItemHasContents); |
197 | } |
198 | |
199 | QQuickShaderEffectSource::~QQuickShaderEffectSource() |
200 | { |
201 | if (window()) { |
202 | window()->scheduleRenderJob(job: new QQuickShaderEffectSourceCleanup(m_texture, m_provider), |
203 | schedule: QQuickWindow::AfterSynchronizingStage); |
204 | } else { |
205 | // If we don't have a window, these should already have been |
206 | // released in invalidateSG or in releaseResrouces() |
207 | Q_ASSERT(!m_texture); |
208 | Q_ASSERT(!m_provider); |
209 | } |
210 | |
211 | if (m_sourceItem) { |
212 | QQuickItemPrivate *sd = QQuickItemPrivate::get(item: m_sourceItem); |
213 | sd->removeItemChangeListener(this, types: QQuickItemPrivate::Geometry); |
214 | sd->derefFromEffectItem(unhide: m_hideSource); |
215 | if (window()) |
216 | sd->derefWindow(); |
217 | } |
218 | } |
219 | |
220 | void QQuickShaderEffectSource::ensureTexture() |
221 | { |
222 | if (m_texture) |
223 | return; |
224 | |
225 | Q_ASSERT_X(QQuickItemPrivate::get(this)->window |
226 | && QQuickItemPrivate::get(this)->sceneGraphRenderContext() |
227 | && QThread::currentThread() == QQuickItemPrivate::get(this)->sceneGraphRenderContext()->thread(), |
228 | "QQuickShaderEffectSource::ensureTexture" , |
229 | "Cannot be used outside the rendering thread" ); |
230 | |
231 | QSGRenderContext *rc = QQuickItemPrivate::get(item: this)->sceneGraphRenderContext(); |
232 | m_texture = rc->sceneGraphContext()->createLayer(renderContext: rc); |
233 | connect(sender: QQuickItemPrivate::get(item: this)->window, SIGNAL(sceneGraphInvalidated()), receiver: m_texture, SLOT(invalidated()), Qt::DirectConnection); |
234 | connect(sender: m_texture, SIGNAL(updateRequested()), receiver: this, SLOT(update())); |
235 | connect(sender: m_texture, SIGNAL(scheduledUpdateCompleted()), receiver: this, SIGNAL(scheduledUpdateCompleted())); |
236 | } |
237 | |
238 | static void get_wrap_mode(QQuickShaderEffectSource::WrapMode mode, QSGTexture::WrapMode *hWrap, QSGTexture::WrapMode *vWrap); |
239 | |
240 | QSGTextureProvider *QQuickShaderEffectSource::textureProvider() const |
241 | { |
242 | const QQuickItemPrivate *d = QQuickItemPrivate::get(item: this); |
243 | if (!d->window || !d->sceneGraphRenderContext() || QThread::currentThread() != d->sceneGraphRenderContext()->thread()) { |
244 | qWarning(msg: "QQuickShaderEffectSource::textureProvider: can only be queried on the rendering thread of an exposed window" ); |
245 | return nullptr; |
246 | } |
247 | |
248 | if (!m_provider) { |
249 | const_cast<QQuickShaderEffectSource *>(this)->m_provider = new QQuickShaderEffectSourceTextureProvider(); |
250 | const_cast<QQuickShaderEffectSource *>(this)->ensureTexture(); |
251 | connect(sender: m_texture, SIGNAL(updateRequested()), receiver: m_provider, SIGNAL(textureChanged())); |
252 | |
253 | get_wrap_mode(mode: m_wrapMode, hWrap: &m_provider->horizontalWrap, vWrap: &m_provider->verticalWrap); |
254 | m_provider->mipmapFiltering = mipmap() ? QSGTexture::Linear : QSGTexture::None; |
255 | m_provider->filtering = smooth() ? QSGTexture::Linear : QSGTexture::Nearest; |
256 | m_provider->sourceTexture = m_texture; |
257 | } |
258 | return m_provider; |
259 | } |
260 | |
261 | /*! |
262 | \qmlproperty enumeration QtQuick::ShaderEffectSource::wrapMode |
263 | |
264 | This property defines the OpenGL wrap modes associated with the texture. |
265 | Modifying this property makes most sense when the item is used as a |
266 | source texture of a \l ShaderEffect. |
267 | |
268 | The default value is \c{ShaderEffectSource.ClampToEdge}. |
269 | |
270 | \list |
271 | \li ShaderEffectSource.ClampToEdge - GL_CLAMP_TO_EDGE both horizontally and vertically |
272 | \li ShaderEffectSource.RepeatHorizontally - GL_REPEAT horizontally, GL_CLAMP_TO_EDGE vertically |
273 | \li ShaderEffectSource.RepeatVertically - GL_CLAMP_TO_EDGE horizontally, GL_REPEAT vertically |
274 | \li ShaderEffectSource.Repeat - GL_REPEAT both horizontally and vertically |
275 | \endlist |
276 | |
277 | \note Some OpenGL ES 2 implementations do not support the GL_REPEAT |
278 | wrap mode with non-power-of-two textures. |
279 | */ |
280 | |
281 | QQuickShaderEffectSource::WrapMode QQuickShaderEffectSource::wrapMode() const |
282 | { |
283 | return m_wrapMode; |
284 | } |
285 | |
286 | void QQuickShaderEffectSource::setWrapMode(WrapMode mode) |
287 | { |
288 | if (mode == m_wrapMode) |
289 | return; |
290 | m_wrapMode = mode; |
291 | update(); |
292 | emit wrapModeChanged(); |
293 | } |
294 | |
295 | /*! |
296 | \qmlproperty Item QtQuick::ShaderEffectSource::sourceItem |
297 | |
298 | This property holds the item to be rendered into the texture. |
299 | Setting this to null while \l live is true, will release the texture |
300 | resources. |
301 | */ |
302 | |
303 | QQuickItem *QQuickShaderEffectSource::sourceItem() const |
304 | { |
305 | return m_sourceItem; |
306 | } |
307 | |
308 | void QQuickShaderEffectSource::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &) |
309 | { |
310 | Q_ASSERT(item == m_sourceItem); |
311 | Q_UNUSED(item); |
312 | if (change.sizeChange()) |
313 | update(); |
314 | } |
315 | |
316 | void QQuickShaderEffectSource::setSourceItem(QQuickItem *item) |
317 | { |
318 | if (item == m_sourceItem) |
319 | return; |
320 | if (m_sourceItem) { |
321 | QQuickItemPrivate *d = QQuickItemPrivate::get(item: m_sourceItem); |
322 | d->derefFromEffectItem(unhide: m_hideSource); |
323 | d->removeItemChangeListener(this, types: QQuickItemPrivate::Geometry); |
324 | disconnect(sender: m_sourceItem, SIGNAL(destroyed(QObject*)), receiver: this, SLOT(sourceItemDestroyed(QObject*))); |
325 | if (window()) |
326 | d->derefWindow(); |
327 | } |
328 | |
329 | m_sourceItem = item; |
330 | |
331 | if (m_sourceItem) { |
332 | if (window() == m_sourceItem->window() |
333 | || (window() == nullptr && m_sourceItem->window()) |
334 | || (m_sourceItem->window() == nullptr && window())) { |
335 | QQuickItemPrivate *d = QQuickItemPrivate::get(item); |
336 | // 'item' needs a window to get a scene graph node. It usually gets one through its |
337 | // parent, but if the source item is "inline" rather than a reference -- i.e. |
338 | // "sourceItem: Item { }" instead of "sourceItem: foo" -- it will not get a parent. |
339 | // In those cases, 'item' should get the window from 'this'. |
340 | if (window()) |
341 | d->refWindow(window()); |
342 | else if (m_sourceItem->window()) |
343 | d->refWindow(m_sourceItem->window()); |
344 | d->refFromEffectItem(hide: m_hideSource); |
345 | d->addItemChangeListener(listener: this, types: QQuickItemPrivate::Geometry); |
346 | connect(sender: m_sourceItem, SIGNAL(destroyed(QObject*)), receiver: this, SLOT(sourceItemDestroyed(QObject*))); |
347 | } else { |
348 | qWarning(msg: "ShaderEffectSource: sourceItem and ShaderEffectSource must both be children of the same window." ); |
349 | m_sourceItem = nullptr; |
350 | } |
351 | } |
352 | update(); |
353 | emit sourceItemChanged(); |
354 | } |
355 | |
356 | void QQuickShaderEffectSource::sourceItemDestroyed(QObject *item) |
357 | { |
358 | Q_ASSERT(item == m_sourceItem); |
359 | Q_UNUSED(item); |
360 | m_sourceItem = nullptr; |
361 | update(); |
362 | emit sourceItemChanged(); |
363 | } |
364 | |
365 | |
366 | /*! |
367 | \qmlproperty rect QtQuick::ShaderEffectSource::sourceRect |
368 | |
369 | This property defines which rectangular area of the \l sourceItem to |
370 | render into the texture. The source rectangle can be larger than |
371 | \l sourceItem itself. If the rectangle is null, which is the default, |
372 | the whole \l sourceItem is rendered to texture. |
373 | */ |
374 | |
375 | QRectF QQuickShaderEffectSource::sourceRect() const |
376 | { |
377 | return m_sourceRect; |
378 | } |
379 | |
380 | void QQuickShaderEffectSource::setSourceRect(const QRectF &rect) |
381 | { |
382 | if (rect == m_sourceRect) |
383 | return; |
384 | m_sourceRect = rect; |
385 | update(); |
386 | emit sourceRectChanged(); |
387 | } |
388 | |
389 | /*! |
390 | \qmlproperty size QtQuick::ShaderEffectSource::textureSize |
391 | |
392 | This property holds the requested size of the texture. If it is empty, |
393 | which is the default, the size of the source rectangle is used. |
394 | |
395 | \note Some platforms have a limit on how small framebuffer objects can be, |
396 | which means the actual texture size might be larger than the requested |
397 | size. |
398 | */ |
399 | |
400 | QSize QQuickShaderEffectSource::textureSize() const |
401 | { |
402 | return m_textureSize; |
403 | } |
404 | |
405 | void QQuickShaderEffectSource::setTextureSize(const QSize &size) |
406 | { |
407 | if (size == m_textureSize) |
408 | return; |
409 | m_textureSize = size; |
410 | update(); |
411 | emit textureSizeChanged(); |
412 | } |
413 | |
414 | /*! |
415 | \qmlproperty enumeration QtQuick::ShaderEffectSource::format |
416 | |
417 | This property defines the internal OpenGL format of the texture. |
418 | Modifying this property makes most sense when the item is used as a |
419 | source texture of a \l ShaderEffect. Depending on the OpenGL |
420 | implementation, this property might allow you to save some texture memory. |
421 | |
422 | \list |
423 | \li ShaderEffectSource.Alpha - GL_ALPHA |
424 | \li ShaderEffectSource.RGB - GL_RGB |
425 | \li ShaderEffectSource.RGBA - GL_RGBA |
426 | \endlist |
427 | |
428 | \note Some OpenGL implementations do not support the GL_ALPHA format. |
429 | */ |
430 | |
431 | QQuickShaderEffectSource::Format QQuickShaderEffectSource::format() const |
432 | { |
433 | return m_format; |
434 | } |
435 | |
436 | void QQuickShaderEffectSource::setFormat(QQuickShaderEffectSource::Format format) |
437 | { |
438 | if (format == m_format) |
439 | return; |
440 | m_format = format; |
441 | update(); |
442 | emit formatChanged(); |
443 | } |
444 | |
445 | /*! |
446 | \qmlproperty bool QtQuick::ShaderEffectSource::live |
447 | |
448 | If this property is true, the texture is updated whenever the |
449 | \l sourceItem updates. Otherwise, it will be a frozen image, even if |
450 | \l sourceItem is assigned a new item. The property is true by default. |
451 | */ |
452 | |
453 | bool QQuickShaderEffectSource::live() const |
454 | { |
455 | return m_live; |
456 | } |
457 | |
458 | void QQuickShaderEffectSource::setLive(bool live) |
459 | { |
460 | if (live == m_live) |
461 | return; |
462 | m_live = live; |
463 | update(); |
464 | emit liveChanged(); |
465 | } |
466 | |
467 | /*! |
468 | \qmlproperty bool QtQuick::ShaderEffectSource::hideSource |
469 | |
470 | If this property is true, the \l sourceItem is hidden, though it will still |
471 | be rendered into the texture. As opposed to hiding the \l sourceItem by |
472 | setting \l{Item::visible}{visible} to false, setting this property to true |
473 | will not prevent mouse or keyboard input from reaching \l sourceItem. |
474 | The property is useful when the ShaderEffectSource is anchored on top of, |
475 | and meant to replace the \l sourceItem. |
476 | */ |
477 | |
478 | bool QQuickShaderEffectSource::hideSource() const |
479 | { |
480 | return m_hideSource; |
481 | } |
482 | |
483 | void QQuickShaderEffectSource::setHideSource(bool hide) |
484 | { |
485 | if (hide == m_hideSource) |
486 | return; |
487 | if (m_sourceItem) { |
488 | QQuickItemPrivate::get(item: m_sourceItem)->refFromEffectItem(hide); |
489 | QQuickItemPrivate::get(item: m_sourceItem)->derefFromEffectItem(unhide: m_hideSource); |
490 | } |
491 | m_hideSource = hide; |
492 | update(); |
493 | emit hideSourceChanged(); |
494 | } |
495 | |
496 | /*! |
497 | \qmlproperty bool QtQuick::ShaderEffectSource::mipmap |
498 | |
499 | If this property is true, mipmaps are generated for the texture. |
500 | |
501 | \note Some OpenGL ES 2 implementations do not support mipmapping of |
502 | non-power-of-two textures. |
503 | */ |
504 | |
505 | bool QQuickShaderEffectSource::mipmap() const |
506 | { |
507 | return m_mipmap; |
508 | } |
509 | |
510 | void QQuickShaderEffectSource::setMipmap(bool enabled) |
511 | { |
512 | if (enabled == m_mipmap) |
513 | return; |
514 | m_mipmap = enabled; |
515 | update(); |
516 | emit mipmapChanged(); |
517 | } |
518 | |
519 | /*! |
520 | \qmlproperty bool QtQuick::ShaderEffectSource::recursive |
521 | |
522 | Set this property to true if the ShaderEffectSource has a dependency on |
523 | itself. ShaderEffectSources form a dependency chain, where one |
524 | ShaderEffectSource can be part of the \l sourceItem of another. |
525 | If there is a loop in this chain, a ShaderEffectSource could end up trying |
526 | to render into the same texture it is using as source, which is not allowed |
527 | by OpenGL. When this property is set to true, an extra texture is allocated |
528 | so that ShaderEffectSource can keep a copy of the texture from the previous |
529 | frame. It can then render into one texture and use the texture from the |
530 | previous frame as source. |
531 | |
532 | Setting both this property and \l live to true will cause the scene graph |
533 | to render continuously. Since the ShaderEffectSource depends on itself, |
534 | updating it means that it immediately becomes dirty again. |
535 | */ |
536 | |
537 | bool QQuickShaderEffectSource::recursive() const |
538 | { |
539 | return m_recursive; |
540 | } |
541 | |
542 | void QQuickShaderEffectSource::setRecursive(bool enabled) |
543 | { |
544 | if (enabled == m_recursive) |
545 | return; |
546 | m_recursive = enabled; |
547 | emit recursiveChanged(); |
548 | } |
549 | |
550 | /*! |
551 | \qmlproperty enumeration QtQuick::ShaderEffectSource::textureMirroring |
552 | \since 5.6 |
553 | |
554 | This property defines how the generated OpenGL texture should be mirrored. |
555 | The default value is \c{ShaderEffectSource.MirrorVertically}. |
556 | Custom mirroring can be useful if the generated texture is directly accessed by custom shaders, |
557 | such as those specified by ShaderEffect. Mirroring has no effect on the UI representation of |
558 | the ShaderEffectSource item itself. |
559 | |
560 | \list |
561 | \li ShaderEffectSource.NoMirroring - No mirroring |
562 | \li ShaderEffectSource.MirrorHorizontally - The generated texture is flipped along X-axis. |
563 | \li ShaderEffectSource.MirrorVertically - The generated texture is flipped along Y-axis. |
564 | \endlist |
565 | */ |
566 | |
567 | QQuickShaderEffectSource::TextureMirroring QQuickShaderEffectSource::textureMirroring() const |
568 | { |
569 | return QQuickShaderEffectSource::TextureMirroring(m_textureMirroring); |
570 | } |
571 | |
572 | void QQuickShaderEffectSource::setTextureMirroring(TextureMirroring mirroring) |
573 | { |
574 | if (mirroring == QQuickShaderEffectSource::TextureMirroring(m_textureMirroring)) |
575 | return; |
576 | m_textureMirroring = mirroring; |
577 | update(); |
578 | emit textureMirroringChanged(); |
579 | } |
580 | |
581 | /*! |
582 | \qmlproperty int QtQuick::ShaderEffectSource::samples |
583 | \since 5.10 |
584 | |
585 | This property allows requesting multisampled rendering. |
586 | |
587 | By default multisampling is enabled whenever multisampling is enabled for |
588 | the entire window, assuming the scenegraph renderer in use and the |
589 | underlying graphics API supports this. |
590 | |
591 | By setting the value to 2, 4, etc. multisampled rendering can be requested |
592 | for a part of the scene without enabling multisampling for the entire |
593 | scene. This way multisampling is applied only to a given subtree, which can |
594 | lead to significant performance gains since multisampling is not applied to |
595 | other parts of the scene. |
596 | |
597 | \note Enabling multisampling can be potentially expensive regardless of the |
598 | layer's size, as it incurs a hardware and driver dependent performance and |
599 | memory cost. |
600 | |
601 | \note This property is only functional when support for multisample |
602 | renderbuffers and framebuffer blits is available. Otherwise the value is |
603 | silently ignored. |
604 | */ |
605 | int QQuickShaderEffectSource::samples() const |
606 | { |
607 | return m_samples; |
608 | } |
609 | |
610 | void QQuickShaderEffectSource::setSamples(int count) |
611 | { |
612 | if (count == m_samples) |
613 | return; |
614 | m_samples = count; |
615 | update(); |
616 | emit samplesChanged(); |
617 | } |
618 | |
619 | /*! |
620 | \qmlmethod QtQuick::ShaderEffectSource::scheduleUpdate() |
621 | |
622 | Schedules a re-rendering of the texture for the next frame. |
623 | Use this to update the texture when \l live is false. |
624 | */ |
625 | |
626 | void QQuickShaderEffectSource::scheduleUpdate() |
627 | { |
628 | if (m_grab) |
629 | return; |
630 | m_grab = true; |
631 | update(); |
632 | } |
633 | |
634 | static void get_wrap_mode(QQuickShaderEffectSource::WrapMode mode, QSGTexture::WrapMode *hWrap, QSGTexture::WrapMode *vWrap) |
635 | { |
636 | switch (mode) { |
637 | case QQuickShaderEffectSource::RepeatHorizontally: |
638 | *hWrap = QSGTexture::Repeat; |
639 | *vWrap = QSGTexture::ClampToEdge; |
640 | break; |
641 | case QQuickShaderEffectSource::RepeatVertically: |
642 | *vWrap = QSGTexture::Repeat; |
643 | *hWrap = QSGTexture::ClampToEdge; |
644 | break; |
645 | case QQuickShaderEffectSource::Repeat: |
646 | *hWrap = *vWrap = QSGTexture::Repeat; |
647 | break; |
648 | default: |
649 | // QQuickShaderEffectSource::ClampToEdge |
650 | *hWrap = *vWrap = QSGTexture::ClampToEdge; |
651 | break; |
652 | } |
653 | } |
654 | |
655 | |
656 | void QQuickShaderEffectSource::releaseResources() |
657 | { |
658 | if (m_texture || m_provider) { |
659 | window()->scheduleRenderJob(job: new QQuickShaderEffectSourceCleanup(m_texture, m_provider), |
660 | schedule: QQuickWindow::AfterSynchronizingStage); |
661 | m_texture = nullptr; |
662 | m_provider = nullptr; |
663 | } |
664 | } |
665 | |
666 | class QQuickShaderSourceAttachedNode : public QObject, public QSGNode |
667 | { |
668 | Q_OBJECT |
669 | public: |
670 | Q_SLOT void markTextureDirty() { |
671 | QSGNode *pn = QSGNode::parent(); |
672 | if (pn) { |
673 | Q_ASSERT(pn->type() == QSGNode::GeometryNodeType); |
674 | pn->markDirty(bits: DirtyMaterial); |
675 | } |
676 | } |
677 | }; |
678 | |
679 | QSGNode *QQuickShaderEffectSource::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) |
680 | { |
681 | if (!m_sourceItem || m_sourceItem->width() <= 0 || m_sourceItem->height() <= 0) { |
682 | if (m_texture) |
683 | m_texture->setItem(nullptr); |
684 | delete oldNode; |
685 | return nullptr; |
686 | } |
687 | |
688 | ensureTexture(); |
689 | |
690 | m_texture->setLive(m_live); |
691 | m_texture->setItem(QQuickItemPrivate::get(item: m_sourceItem)->itemNode()); |
692 | QRectF sourceRect = m_sourceRect.width() == 0 || m_sourceRect.height() == 0 |
693 | ? QRectF(0, 0, m_sourceItem->width(), m_sourceItem->height()) |
694 | : m_sourceRect; |
695 | m_texture->setRect(sourceRect); |
696 | QSize textureSize = m_textureSize.isEmpty() |
697 | ? QSize(qCeil(v: qAbs(t: sourceRect.width())), qCeil(v: qAbs(t: sourceRect.height()))) |
698 | : m_textureSize; |
699 | Q_ASSERT(!textureSize.isEmpty()); |
700 | |
701 | QQuickItemPrivate *d = static_cast<QQuickItemPrivate *>(QObjectPrivate::get(o: this)); |
702 | |
703 | // Crate large textures on high-dpi displays. |
704 | if (sourceItem()) |
705 | textureSize *= d->window->effectiveDevicePixelRatio(); |
706 | |
707 | const QSize minTextureSize = d->sceneGraphContext()->minimumFBOSize(); |
708 | // Keep power-of-two by doubling the size. |
709 | while (textureSize.width() < minTextureSize.width()) |
710 | textureSize.rwidth() *= 2; |
711 | while (textureSize.height() < minTextureSize.height()) |
712 | textureSize.rheight() *= 2; |
713 | |
714 | m_texture->setDevicePixelRatio(d->window->effectiveDevicePixelRatio()); |
715 | m_texture->setSize(textureSize); |
716 | m_texture->setRecursive(m_recursive); |
717 | m_texture->setFormat(m_format); |
718 | m_texture->setHasMipmaps(m_mipmap); |
719 | m_texture->setMirrorHorizontal(m_textureMirroring & MirrorHorizontally); |
720 | m_texture->setMirrorVertical(m_textureMirroring & MirrorVertically); |
721 | m_texture->setSamples(m_samples); |
722 | |
723 | if (m_grab) |
724 | m_texture->scheduleUpdate(); |
725 | m_grab = false; |
726 | |
727 | QSGTexture::Filtering filtering = QQuickItemPrivate::get(item: this)->smooth |
728 | ? QSGTexture::Linear |
729 | : QSGTexture::Nearest; |
730 | QSGTexture::Filtering mmFiltering = m_mipmap ? filtering : QSGTexture::None; |
731 | QSGTexture::WrapMode hWrap, vWrap; |
732 | get_wrap_mode(mode: m_wrapMode, hWrap: &hWrap, vWrap: &vWrap); |
733 | |
734 | if (m_provider) { |
735 | m_provider->mipmapFiltering = mmFiltering; |
736 | m_provider->filtering = filtering; |
737 | m_provider->horizontalWrap = hWrap; |
738 | m_provider->verticalWrap = vWrap; |
739 | } |
740 | |
741 | // Don't create the paint node if we're not spanning any area |
742 | if (width() <= 0 || height() <= 0) { |
743 | delete oldNode; |
744 | return nullptr; |
745 | } |
746 | |
747 | QSGInternalImageNode *node = static_cast<QSGInternalImageNode *>(oldNode); |
748 | if (!node) { |
749 | node = d->sceneGraphContext()->createInternalImageNode(renderContext: d->sceneGraphRenderContext()); |
750 | node->setFlag(QSGNode::UsePreprocess); |
751 | node->setTexture(m_texture); |
752 | QQuickShaderSourceAttachedNode *attached = new QQuickShaderSourceAttachedNode; |
753 | node->appendChildNode(node: attached); |
754 | connect(sender: m_texture, SIGNAL(updateRequested()), receiver: attached, SLOT(markTextureDirty())); |
755 | } |
756 | |
757 | // If live and recursive, update continuously. |
758 | if (m_live && m_recursive) |
759 | node->markDirty(bits: QSGNode::DirtyMaterial); |
760 | |
761 | node->setMipmapFiltering(mmFiltering); |
762 | node->setFiltering(filtering); |
763 | node->setHorizontalWrapMode(hWrap); |
764 | node->setVerticalWrapMode(vWrap); |
765 | node->setTargetRect(QRectF(0, 0, width(), height())); |
766 | node->setInnerTargetRect(QRectF(0, 0, width(), height())); |
767 | node->update(); |
768 | |
769 | return node; |
770 | } |
771 | |
772 | void QQuickShaderEffectSource::invalidateSceneGraph() |
773 | { |
774 | if (m_texture) |
775 | delete m_texture; |
776 | if (m_provider) |
777 | delete m_provider; |
778 | m_texture = nullptr; |
779 | m_provider = nullptr; |
780 | } |
781 | |
782 | void QQuickShaderEffectSource::itemChange(ItemChange change, const ItemChangeData &value) |
783 | { |
784 | if (change == QQuickItem::ItemSceneChange && m_sourceItem) { |
785 | // See comment in QQuickShaderEffectSource::setSourceItem(). |
786 | if (value.window) |
787 | QQuickItemPrivate::get(item: m_sourceItem)->refWindow(value.window); |
788 | else |
789 | QQuickItemPrivate::get(item: m_sourceItem)->derefWindow(); |
790 | } |
791 | QQuickItem::itemChange(change, value); |
792 | } |
793 | |
794 | #include "qquickshadereffectsource.moc" |
795 | #include "moc_qquickshadereffectsource_p.cpp" |
796 | |
797 | QT_END_NAMESPACE |
798 | |