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