Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickshadereffect.cpp
Go to the documentation of this file.
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// Qt-Security score:significant reason:default
4
5#include <private/qquickshadereffect_p_p.h>
6#include <private/qsgcontextplugin_p.h>
7#include <private/qsgrhisupport_p.h>
8#include <private/qquickwindow_p.h>
9
10QT_BEGIN_NAMESPACE
11
12/*!
13 \qmltype ShaderEffect
14 \nativetype QQuickShaderEffect
15 \inqmlmodule QtQuick
16 \inherits Item
17 \ingroup qtquick-effects
18 \brief Applies custom shaders to a rectangle.
19
20 The ShaderEffect type applies a custom \l{vertexShader}{vertex} and
21 \l{fragmentShader}{fragment (pixel)} shader to a rectangle. It allows
22 adding effects such as drop shadow, blur, colorize and page curl into the
23 QML scene.
24
25 \note Depending on the Qt Quick scenegraph backend in use, the ShaderEffect
26 type may not be supported. For example, with the \c software backend
27 effects will not be rendered at all.
28
29 \section1 Shaders
30
31 In Qt 5, effects were provided in form of GLSL (OpenGL Shading Language)
32 source code, often embedded as strings into QML. Starting with Qt 5.8,
33 referring to files, either local ones or in the Qt resource system, became
34 possible as well.
35
36 In Qt 6, Qt Quick has support for graphics APIs, such as Vulkan, Metal, and
37 Direct3D 11 as well. Therefore, working with GLSL source strings is no
38 longer feasible. Rather, the new shader pipeline is based on compiling
39 Vulkan-compatible GLSL code into \l{https://www.khronos.org/spir/}{SPIR-V},
40 followed by gathering reflection information and translating into other
41 shading languages, such as HLSL, the Metal Shading Language, and various
42 GLSL versions. The resulting assets are packed together into a single
43 package, typically stored in files with an extension of \c{.qsb}. This
44 process is done offline or at application build time at latest. At run
45 time, the scene graph and the underlying graphics abstraction consumes
46 these \c{.qsb} files. Therefore, ShaderEffect expects file (local or qrc)
47 references in Qt 6 in place of inline shader code.
48
49 The \l vertexShader and \l fragmentShader properties are URLs in Qt 6, and
50 work very similarly to \l{Image::source}{Image.source}, for example. Only
51 the \c file and \c qrc schemes are supported with ShaderEffect, however. It
52 is also possible to omit the \c file scheme, allowing to specify a relative
53 path in a convenient way. Such a path is resolved relative to the
54 component's (the \c{.qml} file's) location.
55
56 \section1 Shader Inputs and Resources
57
58 There are two types of input to the \l vertexShader: uniforms and vertex
59 inputs.
60
61 The following inputs are predefined:
62
63 \list
64 \li vec4 qt_Vertex with location 0 - vertex position, the top-left vertex has
65 position (0, 0), the bottom-right (\l{Item::width}{width},
66 \l{Item::height}{height}).
67 \li vec2 qt_MultiTexCoord0 with location 1 - texture coordinate, the top-left
68 coordinate is (0, 0), the bottom-right (1, 1). If \l supportsAtlasTextures
69 is true, coordinates will be based on position in the atlas instead.
70 \endlist
71
72 \note It is only the vertex input location that matters in practice. The
73 names are freely changeable, while the location must always be \c 0 for
74 vertex position, \c 1 for texture coordinates. However, be aware that this
75 applies to vertex inputs only, and is not necessarily true for output
76 variables from the vertex shader that are then used as inputs in the
77 fragment shader (typically, the interpolated texture coordinates).
78
79 The following uniforms are predefined:
80
81 \list
82 \li mat4 qt_Matrix - combined transformation
83 matrix, the product of the matrices from the root item to this
84 ShaderEffect, and an orthogonal projection.
85 \li float qt_Opacity - combined opacity, the product of the
86 opacities from the root item to this ShaderEffect.
87 \endlist
88
89 \note Vulkan-style GLSL has no separate uniform variables. Instead, shaders
90 must always use a uniform block with a binding point of \c 0.
91
92 \note The uniform block layout qualifier must always be \c std140.
93
94 \note Unlike vertex inputs, the predefined names (qt_Matrix, qt_Opacity)
95 must not be changed.
96
97 In addition, any property that can be mapped to a GLSL type can be made
98 available to the shaders. The following list shows how properties are
99 mapped:
100
101 \list
102 \li bool, int, qreal -> bool, int, float - If the type in the shader is not
103 the same as in QML, the value is converted automatically.
104 \li QColor -> vec4 - When colors are passed to the shader, they are first
105 premultiplied. Thus Qt.rgba(0.2, 0.6, 1.0, 0.5) becomes
106 vec4(0.1, 0.3, 0.5, 0.5) in the shader, for example.
107 \li QRect, QRectF -> vec4 - Qt.rect(x, y, w, h) becomes vec4(x, y, w, h) in
108 the shader.
109 \li QPoint, QPointF, QSize, QSizeF -> vec2
110 \li QVector3D -> vec3
111 \li QVector4D -> vec4
112 \li QTransform -> mat3
113 \li QMatrix4x4 -> mat4
114 \li QQuaternion -> vec4, scalar value is \c w.
115 \li \l Image -> sampler2D - Origin is in the top-left corner, and the
116 color values are premultiplied. The texture is provided as is,
117 excluding the Image item's fillMode. To include fillMode, use a
118 ShaderEffectSource or Image::layer::enabled.
119 \li \l ShaderEffectSource -> sampler2D - Origin is in the top-left
120 corner, and the color values are premultiplied.
121 \endlist
122
123 Samplers are still declared as separate uniform variables in the shader
124 code. The shaders are free to choose any binding point for these, except
125 for \c 0 because that is reserved for the uniform block.
126
127 Some shading languages and APIs have a concept of separate image and
128 sampler objects. Qt Quick always works with combined image sampler objects
129 in shaders, as supported by SPIR-V. Therefore shaders supplied for
130 ShaderEffect should always use \c{layout(binding = 1) uniform sampler2D
131 tex;} style sampler declarations. The underlying abstraction layer and the
132 shader pipeline takes care of making this work for all the supported APIs
133 and shading languages, transparently to the applications.
134
135 The QML scene graph back-end may choose to allocate textures in texture
136 atlases. If a texture allocated in an atlas is passed to a ShaderEffect,
137 it is by default copied from the texture atlas into a stand-alone texture
138 so that the texture coordinates span from 0 to 1, and you get the expected
139 wrap modes. However, this will increase the memory usage. To avoid the
140 texture copy, set \l supportsAtlasTextures for simple shaders using
141 qt_MultiTexCoord0, or for each "uniform sampler2D <name>" declare a
142 "uniform vec4 qt_SubRect_<name>" which will be assigned the texture's
143 normalized source rectangle. For stand-alone textures, the source rectangle
144 is [0, 1]x[0, 1]. For textures in an atlas, the source rectangle corresponds
145 to the part of the texture atlas where the texture is stored.
146 The correct way to calculate the texture coordinate for a texture called
147 "source" within a texture atlas is
148 "qt_SubRect_source.xy + qt_SubRect_source.zw * qt_MultiTexCoord0".
149
150 The output from the \l fragmentShader should be premultiplied. If
151 \l blending is enabled, source-over blending is used. However, additive
152 blending can be achieved by outputting zero in the alpha channel.
153
154 \table 70%
155 \row
156 \li \image declarative-shadereffectitem.png
157 \li \qml
158 import QtQuick 2.0
159
160 Rectangle {
161 width: 200; height: 100
162 Row {
163 Image { id: img;
164 sourceSize { width: 100; height: 100 } source: "qt-logo.png" }
165 ShaderEffect {
166 width: 100; height: 100
167 property variant src: img
168 vertexShader: "myeffect.vert.qsb"
169 fragmentShader: "myeffect.frag.qsb"
170 }
171 }
172 }
173 \endqml
174 \endtable
175
176 The example assumes \c{myeffect.vert} and \c{myeffect.frag} contain
177 Vulkan-style GLSL code, processed by the \c qsb tool in order to generate
178 the \c{.qsb} files.
179
180 \badcode
181 #version 440
182 layout(location = 0) in vec4 qt_Vertex;
183 layout(location = 1) in vec2 qt_MultiTexCoord0;
184 layout(location = 0) out vec2 coord;
185 layout(std140, binding = 0) uniform buf {
186 mat4 qt_Matrix;
187 float qt_Opacity;
188 };
189 void main() {
190 coord = qt_MultiTexCoord0;
191 gl_Position = qt_Matrix * qt_Vertex;
192 }
193 \endcode
194
195 \badcode
196 #version 440
197 layout(location = 0) in vec2 coord;
198 layout(location = 0) out vec4 fragColor;
199 layout(std140, binding = 0) uniform buf {
200 mat4 qt_Matrix;
201 float qt_Opacity;
202 };
203 layout(binding = 1) uniform sampler2D src;
204 void main() {
205 vec4 tex = texture(src, coord);
206 fragColor = vec4(vec3(dot(tex.rgb, vec3(0.344, 0.5, 0.156))), tex.a) * qt_Opacity;
207 }
208 \endcode
209
210 \note Scene Graph textures have origin in the top-left corner rather than
211 bottom-left which is common in OpenGL.
212
213 \section1 Having One Shader Only
214
215 Specifying both \l vertexShader and \l fragmentShader is not mandatory.
216 Many ShaderEffect implementations will want to provide a fragment shader
217 only in practice, while relying on the default, built-in vertex shader.
218
219 The default vertex shader passes the texture coordinate along to the
220 fragment shader as \c{vec2 qt_TexCoord0} at location \c 0.
221
222 The default fragment shader expects the texture coordinate to be passed
223 from the vertex shader as \c{vec2 qt_TexCoord0} at location \c 0, and it
224 samples from a sampler2D named \c source at binding point \c 1.
225
226 \warning When only one of the shaders is specified, the writer of the
227 shader must be aware of the uniform block layout expected by the default
228 shaders: qt_Matrix must always be at offset 0, followed by qt_Opacity at
229 offset 64. Any custom uniforms must be placed after these two. This is
230 mandatory even when the application-provided shader does not use the matrix
231 or the opacity, because at run time there is one single uniform buffer that
232 is exposed to both the vertex and fragment shader.
233
234 \warning Unlike with vertex inputs, passing data between the vertex and
235 fragment shader may, depending on the underlying graphics API, require the
236 same names to be used, a matching location is not always sufficient. Most
237 prominently, when specifying a fragment shader while relying on the default,
238 built-in vertex shader, the texture coordinates are passed on as \c
239 qt_TexCoord0 at location \c 0, and therefore it is strongly advised that the
240 fragment shader declares the input with the same name
241 (qt_TexCoord0). Failing to do so may lead to issues on some platforms, for
242 example when running with a non-core profile OpenGL context where the
243 underlying GLSL shader source code has no location qualifiers and matching
244 is based on the variable names during to shader linking process.
245
246 \section1 ShaderEffect and Item Layers
247
248 The ShaderEffect type can be combined with \l {Item Layers} {layered items}.
249
250 \table
251 \row
252 \li \b {Layer with effect disabled} \inlineimage qml-shadereffect-nolayereffect.png
253 \li \b {Layer with effect enabled} \inlineimage qml-shadereffect-layereffect.png
254 \row
255 \li \qml
256 Item {
257 id: layerRoot
258 layer.enabled: true
259 layer.effect: ShaderEffect {
260 fragmentShader: "effect.frag.qsb"
261 }
262 }
263 \endqml
264
265 \badcode
266 #version 440
267 layout(location = 0) in vec2 qt_TexCoord0;
268 layout(location = 0) out vec4 fragColor;
269 layout(std140, binding = 0) uniform buf {
270 mat4 qt_Matrix;
271 float qt_Opacity;
272 };
273 layout(binding = 1) uniform sampler2D source;
274 void main() {
275 vec4 p = texture(source, qt_TexCoord0);
276 float g = dot(p.xyz, vec3(0.344, 0.5, 0.156));
277 fragColor = vec4(g, g, g, p.a) * qt_Opacity;
278 }
279 \endcode
280 \endtable
281
282 It is also possible to combine multiple layered items:
283
284 \table
285 \row
286 \li \inlineimage qml-shadereffect-opacitymask.png
287 \row
288 \li \qml
289 Rectangle {
290 id: gradientRect;
291 width: 10
292 height: 10
293 gradient: Gradient {
294 GradientStop { position: 0; color: "white" }
295 GradientStop { position: 1; color: "steelblue" }
296 }
297 visible: false; // should not be visible on screen.
298 layer.enabled: true;
299 layer.smooth: true
300 }
301 Text {
302 id: textItem
303 font.pixelSize: 48
304 text: "Gradient Text"
305 anchors.centerIn: parent
306 layer.enabled: true
307 // This item should be used as the 'mask'
308 layer.samplerName: "maskSource"
309 layer.effect: ShaderEffect {
310 property var colorSource: gradientRect;
311 fragmentShader: "mask.frag.qsb"
312 }
313 }
314 \endqml
315
316 \badcode
317 #version 440
318 layout(location = 0) in vec2 qt_TexCoord0;
319 layout(location = 0) out vec4 fragColor;
320 layout(std140, binding = 0) uniform buf {
321 mat4 qt_Matrix;
322 float qt_Opacity;
323 };
324 layout(binding = 1) uniform sampler2D colorSource;
325 layout(binding = 2) uniform sampler2D maskSource;
326 void main() {
327 fragColor = texture(colorSource, qt_TexCoord0)
328 * texture(maskSource, qt_TexCoord0).a
329 * qt_Opacity;
330 }
331 \endcode
332 \endtable
333
334 \section1 Other Notes
335
336 By default, the ShaderEffect consists of four vertices, one for each
337 corner. For non-linear vertex transformations, like page curl, you can
338 specify a fine grid of vertices by specifying a \l mesh resolution.
339
340 \section1 Migrating From Qt 5
341
342 For Qt 5 applications with ShaderEffect items the migration to Qt 6 involves:
343 \list
344 \li Moving the shader code to separate \c{.vert} and \c{.frag} files,
345 \li updating the shaders to Vulkan-compatible GLSL,
346 \li running the \c qsb tool on them,
347 \li including the resulting \c{.qsb} files in the executable with the Qt resource system,
348 \li and referencing the file in the \l vertexShader and \l fragmentShader properties.
349 \endlist
350
351 As described in the \l{Qt Shader Tools} module some of these steps can be
352 automated by letting CMake invoke the \c qsb tool at build time. See \l{Qt
353 Shader Tools Build System Integration} for more information and examples.
354
355 When it comes to updating the shader code, below is an overview of the
356 commonly required changes.
357
358 \table
359 \header
360 \li Vertex shader in Qt 5
361 \li Vertex shader in Qt 6
362 \row
363 \li \badcode
364 attribute highp vec4 qt_Vertex;
365 attribute highp vec2 qt_MultiTexCoord0;
366 varying highp vec2 coord;
367 uniform highp mat4 qt_Matrix;
368 void main() {
369 coord = qt_MultiTexCoord0;
370 gl_Position = qt_Matrix * qt_Vertex;
371 }
372 \endcode
373 \li \badcode
374 #version 440
375 layout(location = 0) in vec4 qt_Vertex;
376 layout(location = 1) in vec2 qt_MultiTexCoord0;
377 layout(location = 0) out vec2 coord;
378 layout(std140, binding = 0) uniform buf {
379 mat4 qt_Matrix;
380 float qt_Opacity;
381 };
382 void main() {
383 coord = qt_MultiTexCoord0;
384 gl_Position = qt_Matrix * qt_Vertex;
385 }
386 \endcode
387 \endtable
388
389 The conversion process mostly involves updating the code to be compatible
390 with
391 \l{https://github.com/KhronosGroup/GLSL/blob/master/extensions/khr/GL_KHR_vulkan_glsl.txt}{GL_KHR_vulkan_glsl}.
392 It is worth noting that Qt Quick uses a subset of the features provided by
393 GLSL and Vulkan, and therefore the conversion process for typical
394 ShaderEffect shaders is usually straightforward.
395
396 \list
397
398 \li The \c version directive should state \c 440 or \c 450, although
399 specifying other GLSL version may work too, because the
400 \l{https://github.com/KhronosGroup/GLSL/blob/master/extensions/khr/GL_KHR_vulkan_glsl.txt}{GL_KHR_vulkan_glsl}
401 extension is written for GLSL 140 and higher.
402
403 \li Inputs and outputs must use the modern GLSL \c in and \c out keywords.
404 In addition, specifying a location is required. The input and output
405 location namespaces are separate, and therefore assigning locations
406 starting from 0 for both is safe.
407
408 \li When it comes to vertex shader inputs, the only possibilities with
409 ShaderEffect are location \c 0 for vertex position (traditionally named \c
410 qt_Vertex) and location \c 1 for texture coordinates (traditionally named
411 \c qt_MultiTexCoord0).
412
413 \li The vertex shader outputs and fragment shader inputs are up to the
414 shader code to define. The fragment shader must have a \c vec4 output at
415 location 0 (typically called \c fragColor). For maximum portability, vertex
416 outputs and fragment inputs should use both the same location number and the
417 same name. When specifying only a fragment shader, the texture coordinates
418 are passed in from the built-in vertex shader as \c{vec2 qt_TexCoord0} at
419 location \c 0, as shown in the example snippets above.
420
421 \li Uniform variables outside a uniform block are not legal. Rather,
422 uniform data must be declared in a uniform block with binding point \c 0.
423
424 \li The uniform block is expected to use the std140 qualifier.
425
426 \li At run time, the vertex and fragment shader will get the same uniform
427 buffer bound to binding point 0. Therefore, as a general rule, the uniform
428 block declarations must be identical between the shaders. This also
429 includes members that are not used in one of the shaders. The member names
430 must match, because with some graphics APIs the uniform block is converted
431 to a traditional struct uniform, transparently to the application.
432
433 \li When providing one of the shaders only, watch out for the fact that the
434 built-in shaders expect \c qt_Matrix and \c qt_Opacity at the top of the
435 uniform block. (more precisely, at offset 0 and 64, respectively) As a
436 general rule, always include these as the first and second members in the
437 block.
438
439 \li In the example the uniform block specifies the block name \c buf. This
440 name can be changed freely, but must match between the shaders. Using an
441 instance name, such as \c{layout(...) uniform buf { ... } instance_name;}
442 is optional. When specified, all accesses to the members must be qualified
443 with instance_name.
444
445 \endlist
446
447 \table
448 \header
449 \li Fragment shader in Qt 5
450 \li Fragment shader in Qt 6
451 \row
452 \li \badcode
453 varying highp vec2 coord;
454 uniform lowp float qt_Opacity;
455 uniform sampler2D src;
456 void main() {
457 lowp vec4 tex = texture2D(src, coord);
458 gl_FragColor = vec4(vec3(dot(tex.rgb,
459 vec3(0.344, 0.5, 0.156))),
460 tex.a) * qt_Opacity;
461 }
462 \endcode
463 \li \badcode
464 #version 440
465 layout(location = 0) in vec2 coord;
466 layout(location = 0) out vec4 fragColor;
467 layout(std140, binding = 0) uniform buf {
468 mat4 qt_Matrix;
469 float qt_Opacity;
470 };
471 layout(binding = 1) uniform sampler2D src;
472 void main() {
473 vec4 tex = texture(src, coord);
474 fragColor = vec4(vec3(dot(tex.rgb,
475 vec3(0.344, 0.5, 0.156))),
476 tex.a) * qt_Opacity;
477 }
478 \endcode
479 \endtable
480
481 \list
482
483 \li Precision qualifiers (\c lowp, \c mediump, \c highp) are not currently used.
484
485 \li Calling built-in GLSL functions must follow the modern GLSL names, most
486 prominently, \c{texture()} instead of \c{texture2D()}.
487
488 \li Samplers must use binding points starting from 1.
489
490 \li When Qt Quick is rendering with \c multiview enabled, e.g. because it is
491 part of a 3D scene rendering in a VR/AR environment where the left and right
492 eye content are generated in a single pass, the ShaderEffect's shaders have
493 to be written with this in mind. With a view count of 2 for example, there
494 will be \c 2 matrices (qt_Matrix is an array of mat4 with two elements). The
495 vertex shader is expected to take \c gl_ViewIndex into account. See the \c
496 Multiview section in the \l{QSB Manual} for general information on creating
497 multiview-capable shaders.
498
499 \endlist
500
501 \sa {Item Layers}, {QSB Manual}, {Qt Shader Tools Build System Integration}
502*/
503
504
505namespace QtPrivate {
507{
508public:
509 typedef std::function<void()> PropChangedFunc;
510
512 : QSlotObjectBase(&impl), _signalIndex(-1), func(func)
513 { ref(); }
514
515 void setSignalIndex(int idx) { _signalIndex = idx; }
516 int signalIndex() const { return _signalIndex; }
517
518private:
519 int _signalIndex;
520 PropChangedFunc func;
521
522 static void impl(int which, QSlotObjectBase *this_, QObject *, void **a, bool *ret)
523 {
524 auto thiz = static_cast<EffectSlotMapper*>(this_);
525 switch (which) {
526 case Destroy:
527 delete thiz;
528 break;
529 case Call:
530 thiz->func();
531 break;
532 case Compare:
533 *ret = thiz == reinterpret_cast<EffectSlotMapper *>(a[0]);
534 break;
535 case NumOperations: ;
536 }
537 }
538};
539} // namespace QtPrivate
540
541QQuickShaderEffect::QQuickShaderEffect(QQuickItem *parent)
542 : QQuickItem(*new QQuickShaderEffectPrivate, parent)
543{
544 setFlag(QQuickItem::ItemHasContents);
545}
546
547QQuickShaderEffect::~QQuickShaderEffect()
548{
549 Q_D(QQuickShaderEffect);
550 d->inDestructor = true;
551
552 for (int i = 0; i < QQuickShaderEffectPrivate::NShader; ++i) {
553 d->disconnectSignals(QQuickShaderEffectPrivate::Shader(i));
554 d->clearMappers(QQuickShaderEffectPrivate::Shader(i));
555 }
556
557 delete d->m_mgr;
558 d->m_mgr = nullptr;
559}
560
561/*!
562 \qmlproperty url QtQuick::ShaderEffect::fragmentShader
563
564 This property contains a reference to a file with the preprocessed fragment
565 shader package, typically with an extension of \c{.qsb}. The value is
566 treated as a \l{QUrl}{URL}, similarly to other QML types, such as Image. It
567 must either be a local file or use the qrc scheme to access files embedded
568 via the Qt resource system. The URL may be absolute, or relative to the URL
569 of the component.
570
571 \warning Shaders, including \c{.qsb} files, are assumed to be trusted
572 content. Application developers are advised to carefully consider the
573 potential implications before allowing the loading of user-provided content
574 that is not part of the application.
575
576 \sa vertexShader
577*/
578
579QUrl QQuickShaderEffect::fragmentShader() const
580{
581 Q_D(const QQuickShaderEffect);
582 return d->fragmentShader();
583}
584
585void QQuickShaderEffect::setFragmentShader(const QUrl &fileUrl)
586{
587 Q_D(QQuickShaderEffect);
588 d->setFragmentShader(fileUrl);
589}
590
591/*!
592 \qmlproperty url QtQuick::ShaderEffect::vertexShader
593
594 This property contains a reference to a file with the preprocessed vertex
595 shader package, typically with an extension of \c{.qsb}. The value is
596 treated as a \l{QUrl}{URL}, similarly to other QML types, such as Image. It
597 must either be a local file or use the qrc scheme to access files embedded
598 via the Qt resource system. The URL may be absolute, or relative to the URL
599 of the component.
600
601 \warning Shaders, including \c{.qsb} files, are assumed to be trusted
602 content. Application developers are advised to carefully consider the
603 potential implications before allowing the loading of user-provided content
604 that is not part of the application.
605
606 \sa fragmentShader
607*/
608
609QUrl QQuickShaderEffect::vertexShader() const
610{
611 Q_D(const QQuickShaderEffect);
612 return d->vertexShader();
613}
614
615void QQuickShaderEffect::setVertexShader(const QUrl &fileUrl)
616{
617 Q_D(QQuickShaderEffect);
618 d->setVertexShader(fileUrl);
619}
620
621/*!
622 \qmlproperty bool QtQuick::ShaderEffect::blending
623
624 If this property is true, the output from the \l fragmentShader is blended
625 with the background using source-over blend mode. If false, the background
626 is disregarded. Blending decreases the performance, so you should set this
627 property to false when blending is not needed. The default value is true.
628*/
629
630bool QQuickShaderEffect::blending() const
631{
632 Q_D(const QQuickShaderEffect);
633 return d->blending();
634}
635
636void QQuickShaderEffect::setBlending(bool enable)
637{
638 Q_D(QQuickShaderEffect);
639 d->setBlending(enable);
640}
641
642/*!
643 \qmlproperty variant QtQuick::ShaderEffect::mesh
644
645 This property defines the mesh used to draw the ShaderEffect. It can hold
646 any \l GridMesh object.
647 If a size value is assigned to this property, the ShaderEffect implicitly
648 uses a \l GridMesh with the value as
649 \l{GridMesh::resolution}{mesh resolution}. By default, this property is
650 the size 1x1.
651
652 \sa GridMesh
653*/
654
655QVariant QQuickShaderEffect::mesh() const
656{
657 Q_D(const QQuickShaderEffect);
658 return d->mesh();
659}
660
661void QQuickShaderEffect::setMesh(const QVariant &mesh)
662{
663 Q_D(QQuickShaderEffect);
664 d->setMesh(mesh);
665}
666
667/*!
668 \qmlproperty enumeration QtQuick::ShaderEffect::cullMode
669
670 This property defines which sides of the item should be visible.
671
672 \value ShaderEffect.NoCulling Both sides are visible
673 \value ShaderEffect.BackFaceCulling only the front side is visible
674 \value ShaderEffect.FrontFaceCulling only the back side is visible
675
676 The default is NoCulling.
677*/
678
679QQuickShaderEffect::CullMode QQuickShaderEffect::cullMode() const
680{
681 Q_D(const QQuickShaderEffect);
682 return d->cullMode();
683}
684
685void QQuickShaderEffect::setCullMode(CullMode face)
686{
687 Q_D(QQuickShaderEffect);
688 return d->setCullMode(face);
689}
690
691/*!
692 \qmlproperty bool QtQuick::ShaderEffect::supportsAtlasTextures
693
694 Set this property true to confirm that your shader code doesn't rely on
695 qt_MultiTexCoord0 ranging from (0,0) to (1,1) relative to the mesh.
696 In this case the range of qt_MultiTexCoord0 will rather be based on the position
697 of the texture within the atlas. This property currently has no effect if there
698 is less, or more, than one sampler uniform used as input to your shader.
699
700 This differs from providing qt_SubRect_<name> uniforms in that the latter allows
701 drawing one or more textures from the atlas in a single ShaderEffect item, while
702 supportsAtlasTextures allows multiple instances of a ShaderEffect component using
703 a different source image from the atlas to be batched in a single draw.
704 Both prevent a texture from being copied out of the atlas when referenced by a ShaderEffect.
705
706 The default value is false.
707
708 \since 5.4
709 \since QtQuick 2.4
710*/
711
712bool QQuickShaderEffect::supportsAtlasTextures() const
713{
714 Q_D(const QQuickShaderEffect);
715 return d->supportsAtlasTextures();
716}
717
718void QQuickShaderEffect::setSupportsAtlasTextures(bool supports)
719{
720 Q_D(QQuickShaderEffect);
721 d->setSupportsAtlasTextures(supports);
722}
723
724/*!
725 \qmlproperty enumeration QtQuick::ShaderEffect::status
726
727 This property tells the current status of the shaders.
728
729 \value ShaderEffect.Compiled the shader program was successfully compiled and linked.
730 \value ShaderEffect.Uncompiled the shader program has not yet been compiled.
731 \value ShaderEffect.Error the shader program failed to compile or link.
732
733 When setting the fragment or vertex shader source code, the status will
734 become Uncompiled. The first time the ShaderEffect is rendered with new
735 shader source code, the shaders are compiled and linked, and the status is
736 updated to Compiled or Error.
737
738 When runtime compilation is not in use and the shader properties refer to
739 files with bytecode, the status is always Compiled. The contents of the
740 shader is not examined (apart from basic reflection to discover vertex
741 input elements and constant buffer data) until later in the rendering
742 pipeline so potential errors (like layout or root signature mismatches)
743 will only be detected at a later point.
744
745 \sa log
746*/
747
748/*!
749 \qmlproperty string QtQuick::ShaderEffect::log
750
751 This property holds a log of warnings and errors from the latest attempt at
752 compiling the shaders. It is updated at the same time \l status is set to
753 Compiled or Error.
754
755 \note In Qt 6, the shader pipeline promotes compiling and translating the
756 Vulkan-style GLSL shaders offline, or at build time at latest. This does
757 not necessarily mean there is no shader compilation happening at run time,
758 but even if there is, ShaderEffect is not involved in that, and syntax and
759 similar errors should not occur anymore at that stage. Therefore the value
760 of this property is typically empty.
761
762 \sa status
763*/
764
765QString QQuickShaderEffect::log() const
766{
767 Q_D(const QQuickShaderEffect);
768 return d->log();
769}
770
771QQuickShaderEffect::Status QQuickShaderEffect::status() const
772{
773 Q_D(const QQuickShaderEffect);
774 return d->status();
775}
776
777bool QQuickShaderEffect::event(QEvent *e)
778{
779 Q_D(QQuickShaderEffect);
780 d->handleEvent(e);
781 return QQuickItem::event(e);
782}
783
784void QQuickShaderEffect::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
785{
786 Q_D(QQuickShaderEffect);
787 d->handleGeometryChanged(newGeometry, oldGeometry);
788 QQuickItem::geometryChange(newGeometry, oldGeometry);
789}
790
791QSGNode *QQuickShaderEffect::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData)
792{
793 Q_D(QQuickShaderEffect);
794 return d->handleUpdatePaintNode(oldNode, updatePaintNodeData);
795}
796
797void QQuickShaderEffect::componentComplete()
798{
799 Q_D(QQuickShaderEffect);
800 d->maybeUpdateShaders();
801 QQuickItem::componentComplete();
802}
803
804void QQuickShaderEffect::itemChange(ItemChange change, const ItemChangeData &value)
805{
806 Q_D(QQuickShaderEffect);
807 d->handleItemChange(change, value);
808 QQuickItem::itemChange(change, value);
809}
810
811bool QQuickShaderEffect::isComponentComplete() const
812{
813 return QQuickItem::isComponentComplete();
814}
815
816bool QQuickShaderEffect::updateUniformValue(const QByteArray &name, const QVariant &value)
817{
818 auto node = static_cast<QSGShaderEffectNode *>(QQuickItemPrivate::get(this)->paintNode);
819 if (!node)
820 return false;
821
822 Q_D(QQuickShaderEffect);
823 return d->updateUniformValue(name, value, node);
824}
825
826void QQuickShaderEffectPrivate::updatePolish()
827{
828 Q_Q(QQuickShaderEffect);
829 if (!qmlEngine(q))
830 return;
831 maybeUpdateShaders();
832}
833
834constexpr int indexToMappedId(const int shaderType, const int idx)
835{
836 return idx | (shaderType << 16);
837}
838
839constexpr int mappedIdToIndex(const int mappedId)
840{
841 return mappedId & 0xFFFF;
842}
843
844constexpr int mappedIdToShaderType(const int mappedId)
845{
846 return mappedId >> 16;
847}
848
849QQuickShaderEffectPrivate::QQuickShaderEffectPrivate()
850 : m_meshResolution(1, 1)
851 , m_mesh(nullptr)
852 , m_cullMode(QQuickShaderEffect::NoCulling)
853 , m_blending(true)
854 , m_supportsAtlasTextures(false)
855 , m_mgr(nullptr)
856 , m_fragNeedsUpdate(true)
857 , m_vertNeedsUpdate(true)
858{
859 qRegisterMetaType<QSGGuiThreadShaderEffectManager::ShaderInfo::Type>("ShaderInfo::Type");
860 for (int i = 0; i < NShader; ++i)
861 m_inProgress[i] = nullptr;
862}
863
864QQuickShaderEffectPrivate::~QQuickShaderEffectPrivate()
865{
866 Q_ASSERT(m_mgr == nullptr);
867}
868
869void QQuickShaderEffectPrivate::setFragmentShader(const QUrl &fileUrl)
870{
871 Q_Q(QQuickShaderEffect);
872 if (m_fragShader == fileUrl)
873 return;
874
875 m_fragShader = fileUrl;
876
877 m_fragNeedsUpdate = true;
878 if (q->isComponentComplete())
879 maybeUpdateShaders();
880
881 emit q->fragmentShaderChanged();
882}
883
884void QQuickShaderEffectPrivate::setVertexShader(const QUrl &fileUrl)
885{
886 Q_Q(QQuickShaderEffect);
887 if (m_vertShader == fileUrl)
888 return;
889
890 m_vertShader = fileUrl;
891
892 m_vertNeedsUpdate = true;
893 if (q->isComponentComplete())
894 maybeUpdateShaders();
895
896 emit q->vertexShaderChanged();
897}
898
899void QQuickShaderEffectPrivate::setBlending(bool enable)
900{
901 Q_Q(QQuickShaderEffect);
902 if (m_blending == enable)
903 return;
904
905 m_blending = enable;
906 q->update();
907 emit q->blendingChanged();
908}
909
910QVariant QQuickShaderEffectPrivate::mesh() const
911{
912 return m_mesh ? QVariant::fromValue(static_cast<QObject *>(m_mesh))
913 : QVariant::fromValue(m_meshResolution);
914}
915
916void QQuickShaderEffectPrivate::setMesh(const QVariant &mesh)
917{
918 Q_Q(QQuickShaderEffect);
919 QQuickShaderEffectMesh *newMesh = qobject_cast<QQuickShaderEffectMesh *>(qvariant_cast<QObject *>(mesh));
920 if (newMesh && newMesh == m_mesh)
921 return;
922
923 if (m_mesh)
924 QObject::disconnect(m_meshConnection);
925
926 m_mesh = newMesh;
927
928 if (m_mesh) {
929 m_meshConnection = QObject::connect(m_mesh, &QQuickShaderEffectMesh::geometryChanged, q,
930 [this] { markGeometryDirtyAndUpdate(); });
931 } else {
932 if (mesh.canConvert<QSize>()) {
933 m_meshResolution = mesh.toSize();
934 } else {
935 QList<QByteArray> res = mesh.toByteArray().split('x');
936 bool ok = res.size() == 2;
937 if (ok) {
938 int w = res.at(0).toInt(&ok);
939 if (ok) {
940 int h = res.at(1).toInt(&ok);
941 if (ok)
942 m_meshResolution = QSize(w, h);
943 }
944 }
945 if (!ok)
946 qWarning("ShaderEffect: mesh property must be a size or an object deriving from QQuickShaderEffectMesh");
947 }
948 m_defaultMesh.setResolution(m_meshResolution);
949 }
950
951 m_dirty |= QSGShaderEffectNode::DirtyShaderMesh;
952 q->update();
953
954 emit q->meshChanged();
955}
956
957void QQuickShaderEffectPrivate::setCullMode(QQuickShaderEffect::CullMode face)
958{
959 Q_Q(QQuickShaderEffect);
960 if (m_cullMode == face)
961 return;
962
963 m_cullMode = face;
964 q->update();
965 emit q->cullModeChanged();
966}
967
968void QQuickShaderEffectPrivate::setSupportsAtlasTextures(bool supports)
969{
970 Q_Q(QQuickShaderEffect);
971 if (m_supportsAtlasTextures == supports)
972 return;
973
974 m_supportsAtlasTextures = supports;
975 markGeometryDirtyAndUpdate();
976 emit q->supportsAtlasTexturesChanged();
977}
978
979QString QQuickShaderEffectPrivate::parseLog()
980{
981 maybeUpdateShaders();
982 return log();
983}
984
985QString QQuickShaderEffectPrivate::log() const
986{
987 QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager();
988 if (!mgr)
989 return QString();
990
991 return mgr->log();
992}
993
994QQuickShaderEffect::Status QQuickShaderEffectPrivate::status() const
995{
996 QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager();
997 if (!mgr)
998 return QQuickShaderEffect::Uncompiled;
999
1000 return QQuickShaderEffect::Status(mgr->status());
1001}
1002
1003void QQuickShaderEffectPrivate::handleEvent(QEvent *event)
1004{
1005 if (event->type() == QEvent::DynamicPropertyChange) {
1006 const auto propertyName = static_cast<QDynamicPropertyChangeEvent *>(event)->propertyName();
1007 for (int i = 0; i < NShader; ++i) {
1008 const auto mappedId = findMappedShaderVariableId(propertyName, Shader(i));
1009 if (mappedId)
1010 propertyChanged(*mappedId);
1011 }
1012 }
1013}
1014
1015void QQuickShaderEffectPrivate::handleGeometryChanged(const QRectF &, const QRectF &)
1016{
1017 m_dirty |= QSGShaderEffectNode::DirtyShaderGeometry;
1018}
1019
1020QSGNode *QQuickShaderEffectPrivate::handleUpdatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
1021{
1022 Q_Q(QQuickShaderEffect);
1023 QSGShaderEffectNode *node = static_cast<QSGShaderEffectNode *>(oldNode);
1024
1025 if (q->width() <= 0 || q->height() <= 0) {
1026 delete node;
1027 return nullptr;
1028 }
1029
1030 // Do not change anything while a new shader is being reflected or compiled.
1031 if (m_inProgress[Vertex] || m_inProgress[Fragment])
1032 return node;
1033
1034 // The manager should be already created on the gui thread. Just take that instance.
1035 QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager();
1036 if (!mgr) {
1037 delete node;
1038 return nullptr;
1039 }
1040
1041 if (!node) {
1042 QSGRenderContext *rc = QQuickWindowPrivate::get(q->window())->context;
1043 node = rc->sceneGraphContext()->createShaderEffectNode(rc);
1044 if (!node) {
1045 qWarning("No shader effect node");
1046 return nullptr;
1047 }
1048 m_dirty = QSGShaderEffectNode::DirtyShaderAll;
1049 QObject::connect(node, &QSGShaderEffectNode::textureChanged, q, [this] { markGeometryDirtyAndUpdateIfSupportsAtlas(); });
1050 }
1051
1052 QSGShaderEffectNode::SyncData sd;
1053 sd.dirty = m_dirty;
1054 sd.cullMode = QSGShaderEffectNode::CullMode(m_cullMode);
1055 sd.blending = m_blending;
1056 sd.vertex.shader = &m_shaders[Vertex];
1057 sd.vertex.dirtyConstants = &m_dirtyConstants[Vertex];
1058 sd.vertex.dirtyTextures = &m_dirtyTextures[Vertex];
1059 sd.fragment.shader = &m_shaders[Fragment];
1060 sd.fragment.dirtyConstants = &m_dirtyConstants[Fragment];
1061 sd.fragment.dirtyTextures = &m_dirtyTextures[Fragment];
1062 sd.materialTypeCacheKey = q->window();
1063 sd.viewCount = QQuickWindowPrivate::get(q->window())->multiViewCount();
1064
1065 node->syncMaterial(&sd);
1066
1067 if (m_dirty & QSGShaderEffectNode::DirtyShaderMesh) {
1068 node->setGeometry(nullptr);
1069 m_dirty &= ~QSGShaderEffectNode::DirtyShaderMesh;
1070 m_dirty |= QSGShaderEffectNode::DirtyShaderGeometry;
1071 }
1072
1073 if (m_dirty & QSGShaderEffectNode::DirtyShaderGeometry) {
1074 const QRectF rect(0, 0, q->width(), q->height());
1075 QQuickShaderEffectMesh *mesh = m_mesh ? m_mesh : &m_defaultMesh;
1076 QSGGeometry *geometry = node->geometry();
1077
1078 const QRectF srcRect = node->updateNormalizedTextureSubRect(m_supportsAtlasTextures);
1079 geometry = mesh->updateGeometry(geometry, 2, 0, srcRect, rect);
1080
1081 node->setFlag(QSGNode::OwnsGeometry, false);
1082 node->setGeometry(geometry);
1083 node->setFlag(QSGNode::OwnsGeometry, true);
1084
1085 m_dirty &= ~QSGShaderEffectNode::DirtyShaderGeometry;
1086 }
1087
1088 m_dirty = {};
1089 for (int i = 0; i < NShader; ++i) {
1090 m_dirtyConstants[i].clear();
1091 m_dirtyTextures[i].clear();
1092 }
1093
1094 return node;
1095}
1096
1097void QQuickShaderEffectPrivate::maybeUpdateShaders()
1098{
1099 Q_Q(QQuickShaderEffect);
1100 if (m_vertNeedsUpdate)
1101 m_vertNeedsUpdate = !updateShader(Vertex, m_vertShader);
1102 if (m_fragNeedsUpdate)
1103 m_fragNeedsUpdate = !updateShader(Fragment, m_fragShader);
1104 if (m_vertNeedsUpdate || m_fragNeedsUpdate) {
1105 // This function is invoked either from componentComplete or in a
1106 // response to a previous invocation's polish() request. If this is
1107 // case #1 then updateShader can fail due to not having a window or
1108 // scenegraph ready. Schedule the polish to try again later. In case #2
1109 // the backend probably does not have shadereffect support so there is
1110 // nothing to do for us here.
1111 if (!q->window() || !q->window()->isSceneGraphInitialized())
1112 q->polish();
1113 }
1114}
1115
1116bool QQuickShaderEffectPrivate::updateUniformValue(const QByteArray &name, const QVariant &value,
1117 QSGShaderEffectNode *node)
1118{
1119 Q_Q(QQuickShaderEffect);
1120 const auto mappedId = findMappedShaderVariableId(name);
1121 if (!mappedId)
1122 return false;
1123
1124 const Shader type = Shader(mappedIdToShaderType(*mappedId));
1125 const int idx = mappedIdToIndex(*mappedId);
1126
1127 // Update value
1128 m_shaders[type].varData[idx].value = value;
1129
1130 // Insert dirty uniform
1131 QSet<int> dirtyConstants[NShader];
1132 dirtyConstants[type].insert(idx);
1133
1134 // Sync material change
1135 QSGShaderEffectNode::SyncData sd;
1136 sd.dirty = QSGShaderEffectNode::DirtyShaderConstant;
1137 sd.cullMode = QSGShaderEffectNode::CullMode(m_cullMode);
1138 sd.blending = m_blending;
1139 sd.vertex.shader = &m_shaders[Vertex];
1140 sd.vertex.dirtyConstants = &dirtyConstants[Vertex];
1141 sd.vertex.dirtyTextures = {};
1142 sd.fragment.shader = &m_shaders[Fragment];
1143 sd.fragment.dirtyConstants = &dirtyConstants[Fragment];
1144 sd.fragment.dirtyTextures = {};
1145 sd.materialTypeCacheKey = q->window();
1146 sd.viewCount = QQuickWindowPrivate::get(q->window())->multiViewCount();
1147
1148 node->syncMaterial(&sd);
1149
1150 return true;
1151}
1152
1153void QQuickShaderEffectPrivate::handleItemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
1154{
1155 if (inDestructor)
1156 return;
1157
1158 // Move the window ref.
1159 if (change == QQuickItem::ItemSceneChange) {
1160 for (int shaderType = 0; shaderType < NShader; ++shaderType) {
1161 for (const auto &vd : std::as_const(m_shaders[shaderType].varData)) {
1162 if (vd.specialType == QSGShaderEffectNode::VariableData::Source) {
1163 QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(vd.value));
1164 if (source) {
1165 if (value.window)
1166 QQuickItemPrivate::get(source)->refWindow(value.window);
1167 else
1168 QQuickItemPrivate::get(source)->derefWindow();
1169 }
1170 }
1171 }
1172 }
1173 }
1174}
1175
1176QSGGuiThreadShaderEffectManager *QQuickShaderEffectPrivate::shaderEffectManager() const
1177{
1178 Q_Q(const QQuickShaderEffect);
1179 if (!m_mgr) {
1180 // return null if this is not the gui thread and not already created
1181 if (QThread::currentThread() != q->thread())
1182 return m_mgr;
1183 QQuickWindow *w = q->window();
1184 if (w) { // note: just the window, don't care about isSceneGraphInitialized() here
1185 QSGRenderContext *renderContext = QQuickWindowPrivate::get(w)->context;
1186 if (QSGContext *sgContext = renderContext->sceneGraphContext())
1187 m_mgr = sgContext->createGuiThreadShaderEffectManager();
1188 if (m_mgr) {
1189 QObject::connect(m_mgr, &QSGGuiThreadShaderEffectManager::logAndStatusChanged, q, &QQuickShaderEffect::logChanged);
1190 QObject::connect(m_mgr, &QSGGuiThreadShaderEffectManager::logAndStatusChanged, q, &QQuickShaderEffect::statusChanged);
1191 QObject::connect(m_mgr, &QSGGuiThreadShaderEffectManager::shaderCodePrepared, q,
1192 [this](bool ok, QSGGuiThreadShaderEffectManager::ShaderInfo::Type typeHint,
1193 const QUrl &loadUrl, QSGGuiThreadShaderEffectManager::ShaderInfo *result)
1194 { const_cast<QQuickShaderEffectPrivate *>(this)->shaderCodePrepared(ok, typeHint, loadUrl, result); });
1195 }
1196 }
1197 }
1198 return m_mgr;
1199}
1200
1201void QQuickShaderEffectPrivate::disconnectSignals(Shader shaderType)
1202{
1203 Q_Q(QQuickShaderEffect);
1204 for (auto *mapper : m_mappers[shaderType]) {
1205 void *a = mapper;
1206 if (mapper)
1207 QObjectPrivate::disconnect(q, mapper->signalIndex(), &a);
1208 }
1209 for (const auto &vd : std::as_const(m_shaders[shaderType].varData)) {
1210 if (vd.specialType == QSGShaderEffectNode::VariableData::Source) {
1211 QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(vd.value));
1212 if (source) {
1213 if (q->window())
1214 QQuickItemPrivate::get(source)->derefWindow();
1215 auto it = m_destroyedConnections.constFind(source);
1216 if (it != m_destroyedConnections.constEnd()) {
1217 QObject::disconnect(*it);
1218 m_destroyedConnections.erase(it);
1219 }
1220 }
1221 }
1222 }
1223}
1224
1225void QQuickShaderEffectPrivate::clearMappers(QQuickShaderEffectPrivate::Shader shaderType)
1226{
1227 for (auto *mapper : std::as_const(m_mappers[shaderType])) {
1228 if (mapper)
1229 mapper->destroyIfLastRef();
1230 }
1231 m_mappers[shaderType].clear();
1232}
1233
1234static inline QVariant getValueFromProperty(QObject *item, const QMetaObject *itemMetaObject,
1235 const QByteArray &name, int propertyIndex)
1236{
1237 QVariant value;
1238 if (propertyIndex == -1) {
1239 value = item->property(name);
1240 } else {
1241 value = itemMetaObject->property(propertyIndex).read(item);
1242 }
1243 return value;
1244}
1245
1246using QQuickShaderInfoCache = QHash<QUrl, QSGGuiThreadShaderEffectManager::ShaderInfo>;
1248
1250{
1251 shaderInfoCache()->clear();
1252}
1253
1254bool QQuickShaderEffectPrivate::updateShader(Shader shaderType, const QUrl &fileUrl)
1255{
1256 Q_Q(QQuickShaderEffect);
1257 QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager();
1258 if (!mgr)
1259 return false;
1260
1261 const bool texturesSeparate = mgr->hasSeparateSamplerAndTextureObjects();
1262
1263 disconnectSignals(shaderType);
1264
1265 m_shaders[shaderType].shaderInfo.variables.clear();
1266 m_shaders[shaderType].varData.clear();
1267
1268 if (!fileUrl.isEmpty()) {
1269 const QQmlContext *context = qmlContext(q);
1270 const QUrl loadUrl = context ? context->resolvedUrl(fileUrl) : fileUrl;
1271 auto it = shaderInfoCache()->constFind(loadUrl);
1272 if (it != shaderInfoCache()->cend()) {
1273 m_shaders[shaderType].shaderInfo = *it;
1274 m_shaders[shaderType].hasShaderCode = true;
1275 } else {
1276 // Each prepareShaderCode call needs its own work area, hence the
1277 // dynamic alloc. If there are calls in progress, let those run to
1278 // finish, their results can then simply be ignored because
1279 // m_inProgress indicates what we care about.
1280 m_inProgress[shaderType] = new QSGGuiThreadShaderEffectManager::ShaderInfo;
1281 const QSGGuiThreadShaderEffectManager::ShaderInfo::Type typeHint =
1282 shaderType == Vertex ? QSGGuiThreadShaderEffectManager::ShaderInfo::TypeVertex
1283 : QSGGuiThreadShaderEffectManager::ShaderInfo::TypeFragment;
1284 // Figure out what input parameters and variables are used in the
1285 // shader. This is where the data is pulled in from the file.
1286 // (however, if there is compilation involved, that happens at a
1287 // later stage, up to the QRhi backend)
1288 mgr->prepareShaderCode(typeHint, loadUrl, m_inProgress[shaderType]);
1289 // the rest is handled in shaderCodePrepared()
1290 return true;
1291 }
1292 } else {
1293 m_shaders[shaderType].hasShaderCode = false;
1294 if (shaderType == Fragment) {
1295 // With built-in shaders hasShaderCode is set to false and all
1296 // metadata is empty, as it is left up to the node to provide a
1297 // built-in default shader and its metadata. However, in case of
1298 // the built-in fragment shader the value for 'source' has to be
1299 // provided and monitored like with an application-provided shader.
1300 QSGGuiThreadShaderEffectManager::ShaderInfo::Variable v;
1301 v.name = QByteArrayLiteral("source");
1302 v.bindPoint = 1; // fake, must match the default source bindPoint in qquickshadereffectnode.cpp
1303 v.type = texturesSeparate ? QSGGuiThreadShaderEffectManager::ShaderInfo::Texture
1304 : QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler;
1305 m_shaders[shaderType].shaderInfo.variables.append(v);
1306 }
1307 }
1308
1309 updateShaderVars(shaderType);
1310 m_dirty |= QSGShaderEffectNode::DirtyShaders;
1311 q->update();
1312 return true;
1313}
1314
1315void QQuickShaderEffectPrivate::shaderCodePrepared(bool ok, QSGGuiThreadShaderEffectManager::ShaderInfo::Type typeHint,
1316 const QUrl &loadUrl, QSGGuiThreadShaderEffectManager::ShaderInfo *result)
1317{
1318 Q_Q(QQuickShaderEffect);
1319 const Shader shaderType = typeHint == QSGGuiThreadShaderEffectManager::ShaderInfo::TypeVertex ? Vertex : Fragment;
1320
1321 // If another call was made to updateShader() for the same shader type in
1322 // the meantime then our results are useless, just drop them.
1323 if (result != m_inProgress[shaderType]) {
1324 delete result;
1325 return;
1326 }
1327
1328 m_shaders[shaderType].shaderInfo = *result;
1329 delete result;
1330 m_inProgress[shaderType] = nullptr;
1331
1332 if (!ok) {
1333 qWarning("ShaderEffect: shader preparation failed for %s\n%s\n",
1334 qPrintable(loadUrl.toString()), qPrintable(log()));
1335 m_shaders[shaderType].hasShaderCode = false;
1336 return;
1337 }
1338
1339 m_shaders[shaderType].hasShaderCode = true;
1340 shaderInfoCache()->insert(loadUrl, m_shaders[shaderType].shaderInfo);
1341 updateShaderVars(shaderType);
1342 m_dirty |= QSGShaderEffectNode::DirtyShaders;
1343 q->update();
1344}
1345
1346void QQuickShaderEffectPrivate::updateShaderVars(Shader shaderType)
1347{
1348 Q_Q(QQuickShaderEffect);
1349 QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager();
1350 if (!mgr)
1351 return;
1352
1353 const bool texturesSeparate = mgr->hasSeparateSamplerAndTextureObjects();
1354
1355 const int varCount = m_shaders[shaderType].shaderInfo.variables.size();
1356 m_shaders[shaderType].varData.resize(varCount);
1357
1358 // Recreate signal mappers when the shader has changed.
1359 clearMappers(shaderType);
1360
1361 QQmlPropertyCache::ConstPtr propCache = QQmlData::ensurePropertyCache(q);
1362
1363 if (!m_itemMetaObject)
1364 m_itemMetaObject = q->metaObject();
1365
1366 // Hook up the signals to get notified about changes for properties that
1367 // correspond to variables in the shader. Store also the values.
1368 for (int i = 0; i < varCount; ++i) {
1369 const auto &v(m_shaders[shaderType].shaderInfo.variables.at(i));
1370 QSGShaderEffectNode::VariableData &vd(m_shaders[shaderType].varData[i]);
1371 const bool isSpecial = v.name.startsWith("qt_"); // special names not mapped to properties
1372 if (isSpecial) {
1373 if (v.name == "qt_Opacity")
1374 vd.specialType = QSGShaderEffectNode::VariableData::Opacity;
1375 else if (v.name == "qt_Matrix")
1376 vd.specialType = QSGShaderEffectNode::VariableData::Matrix;
1377 else if (v.name.startsWith("qt_SubRect_"))
1378 vd.specialType = QSGShaderEffectNode::VariableData::SubRect;
1379 continue;
1380 }
1381
1382 // The value of a property corresponding to a sampler is the source
1383 // item ref, unless there are separate texture objects in which case
1384 // the sampler is ignored (here).
1385 if (v.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler) {
1386 if (texturesSeparate) {
1387 vd.specialType = QSGShaderEffectNode::VariableData::Unused;
1388 continue;
1389 } else {
1390 vd.specialType = QSGShaderEffectNode::VariableData::Source;
1391 }
1392 } else if (v.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Texture) {
1393 Q_ASSERT(texturesSeparate);
1394 vd.specialType = QSGShaderEffectNode::VariableData::Source;
1395 } else {
1396 vd.specialType = QSGShaderEffectNode::VariableData::None;
1397 }
1398
1399 // Find the property on the ShaderEffect item.
1400 int propIdx = -1;
1401 const QQmlPropertyData *pd = nullptr;
1402 if (propCache) {
1403 pd = propCache->property(QLatin1String(v.name), nullptr, nullptr);
1404 if (pd) {
1405 if (!pd->isFunction())
1406 propIdx = pd->coreIndex();
1407 }
1408 }
1409 if (propIdx >= 0) {
1410 if (pd && !pd->isFunction()) {
1411 if (pd->notifyIndex() == -1) {
1412 qWarning("QQuickShaderEffect: property '%s' does not have notification method!", v.name.constData());
1413 } else {
1414 const int mappedId = indexToMappedId(shaderType, i);
1415 auto mapper = new QtPrivate::EffectSlotMapper([this, mappedId](){
1416 this->propertyChanged(mappedId);
1417 });
1418 m_mappers[shaderType].append(mapper);
1419 mapper->setSignalIndex(m_itemMetaObject->property(propIdx).notifySignal().methodIndex());
1420 Q_ASSERT(q->metaObject() == m_itemMetaObject);
1421 bool ok = QObjectPrivate::connectImpl(q, pd->notifyIndex(), q, nullptr, mapper,
1422 Qt::AutoConnection, nullptr, m_itemMetaObject);
1423 if (!ok)
1424 qWarning() << "Failed to connect to property" << m_itemMetaObject->property(propIdx).name()
1425 << "(" << propIdx << ", signal index" << pd->notifyIndex()
1426 << ") of item" << q;
1427 }
1428 }
1429 } else {
1430 // Do not warn for dynamic properties.
1431 if (!q->property(v.name.constData()).isValid())
1432 qWarning("ShaderEffect: '%s' does not have a matching property", v.name.constData());
1433 }
1434
1435
1436 vd.propertyIndex = propIdx;
1437 vd.value = getValueFromProperty(q, m_itemMetaObject, v.name, vd.propertyIndex);
1438 if (vd.specialType == QSGShaderEffectNode::VariableData::Source) {
1439 QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(vd.value));
1440 if (source) {
1441 if (q->window())
1442 QQuickItemPrivate::get(source)->refWindow(q->window());
1443
1444 // Cannot just pass q as the 'context' for the connect(). The
1445 // order of destruction is...complicated. Having an inline
1446 // source (e.g. source: ShaderEffectSource { ... } in QML would
1447 // emit destroyed() after the connection was already gone. To
1448 // work that around, store the Connection and manually
1449 // disconnect instead.
1450 if (!m_destroyedConnections.contains(source))
1451 m_destroyedConnections.insert(source, QObject::connect(source, &QObject::destroyed, [this](QObject *obj) { sourceDestroyed(obj); }));
1452 }
1453 }
1454 }
1455}
1456
1457std::optional<int> QQuickShaderEffectPrivate::findMappedShaderVariableId(const QByteArray &name) const
1458{
1459 for (int shaderType = 0; shaderType < NShader; ++shaderType) {
1460 const auto &vars = m_shaders[shaderType].shaderInfo.variables;
1461 for (int idx = 0; idx < vars.size(); ++idx) {
1462 if (vars[idx].name == name)
1463 return indexToMappedId(shaderType, idx);
1464 }
1465 }
1466
1467 return {};
1468}
1469
1470std::optional<int> QQuickShaderEffectPrivate::findMappedShaderVariableId(const QByteArray &name, Shader shaderType) const
1471{
1472 const auto &vars = m_shaders[shaderType].shaderInfo.variables;
1473 for (int idx = 0; idx < vars.size(); ++idx) {
1474 if (vars[idx].name == name)
1475 return indexToMappedId(shaderType, idx);
1476 }
1477
1478 return {};
1479}
1480
1481bool QQuickShaderEffectPrivate::sourceIsUnique(QQuickItem *source, Shader typeToSkip, int indexToSkip) const
1482{
1483 for (int shaderType = 0; shaderType < NShader; ++shaderType) {
1484 for (int idx = 0; idx < m_shaders[shaderType].varData.size(); ++idx) {
1485 if (shaderType != typeToSkip || idx != indexToSkip) {
1486 const auto &vd(m_shaders[shaderType].varData[idx]);
1487 if (vd.specialType == QSGShaderEffectNode::VariableData::Source && qvariant_cast<QObject *>(vd.value) == source)
1488 return false;
1489 }
1490 }
1491 }
1492 return true;
1493}
1494
1495void QQuickShaderEffectPrivate::propertyChanged(int mappedId)
1496{
1497 Q_Q(QQuickShaderEffect);
1498 const Shader type = Shader(mappedIdToShaderType(mappedId));
1499 const int idx = mappedIdToIndex(mappedId);
1500 const auto &v(m_shaders[type].shaderInfo.variables[idx]);
1501 auto &vd(m_shaders[type].varData[idx]);
1502
1503 QVariant oldValue = vd.value;
1504 vd.value = getValueFromProperty(q, m_itemMetaObject, v.name, vd.propertyIndex);
1505
1506 if (vd.specialType == QSGShaderEffectNode::VariableData::Source) {
1507 QQuickItem *source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(oldValue));
1508 if (source) {
1509 if (q->window())
1510 QQuickItemPrivate::get(source)->derefWindow();
1511 // If the same source has been attached to two separate
1512 // textures/samplers, then changing one of them would trigger both
1513 // to be disconnected. So check first.
1514 if (sourceIsUnique(source, type, idx)) {
1515 auto it = m_destroyedConnections.constFind(source);
1516 if (it != m_destroyedConnections.constEnd()) {
1517 QObject::disconnect(*it);
1518 m_destroyedConnections.erase(it);
1519 }
1520 }
1521 }
1522
1523 source = qobject_cast<QQuickItem *>(qvariant_cast<QObject *>(vd.value));
1524 if (source) {
1525 // 'source' needs a window to get a scene graph node. It usually gets one through its
1526 // parent, but if the source item is "inline" rather than a reference -- i.e.
1527 // "property variant source: Image { }" instead of "property variant source: foo" -- it
1528 // will not get a parent. In those cases, 'source' should get the window from 'item'.
1529 if (q->window())
1530 QQuickItemPrivate::get(source)->refWindow(q->window());
1531 if (!m_destroyedConnections.contains(source))
1532 m_destroyedConnections.insert(source, QObject::connect(source, &QObject::destroyed, [this](QObject *obj) { sourceDestroyed(obj); }));
1533 }
1534
1535 m_dirty |= QSGShaderEffectNode::DirtyShaderTexture;
1536 m_dirtyTextures[type].insert(idx);
1537
1538 } else {
1539 m_dirty |= QSGShaderEffectNode::DirtyShaderConstant;
1540 m_dirtyConstants[type].insert(idx);
1541 }
1542
1543 q->update();
1544}
1545
1546void QQuickShaderEffectPrivate::sourceDestroyed(QObject *object)
1547{
1548 for (int shaderType = 0; shaderType < NShader; ++shaderType) {
1549 for (auto &vd : m_shaders[shaderType].varData) {
1550 if (vd.specialType == QSGShaderEffectNode::VariableData::Source && vd.value.canConvert<QObject *>()) {
1551 if (qvariant_cast<QObject *>(vd.value) == object)
1552 vd.value = QVariant();
1553 }
1554 }
1555 }
1556}
1557
1558void QQuickShaderEffectPrivate::markGeometryDirtyAndUpdate()
1559{
1560 Q_Q(QQuickShaderEffect);
1561 m_dirty |= QSGShaderEffectNode::DirtyShaderGeometry;
1562 q->update();
1563}
1564
1565void QQuickShaderEffectPrivate::markGeometryDirtyAndUpdateIfSupportsAtlas()
1566{
1567 if (m_supportsAtlasTextures)
1568 markGeometryDirtyAndUpdate();
1569}
1570
1571QT_END_NAMESPACE
1572
1573#include "moc_qquickshadereffect_p.cpp"
std::function< void()> PropChangedFunc
EffectSlotMapper(PropChangedFunc func)
Q_GLOBAL_STATIC(QReadWriteLock, g_updateMutex)
constexpr int mappedIdToShaderType(const int mappedId)
constexpr int mappedIdToIndex(const int mappedId)
static QVariant getValueFromProperty(QObject *item, const QMetaObject *itemMetaObject, const QByteArray &name, int propertyIndex)
void qtquick_shadereffect_purge_gui_thread_shader_cache()
constexpr int indexToMappedId(const int shaderType, const int idx)