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