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

source code of qtdeclarative/src/quick/items/qquickshadereffect.cpp