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 \sa vertexShader
571*/
572
573QUrl QQuickShaderEffect::fragmentShader() const
574{
575 Q_D(const QQuickShaderEffect);
576 return d->fragmentShader();
577}
578
579void QQuickShaderEffect::setFragmentShader(const QUrl &fileUrl)
580{
581 Q_D(QQuickShaderEffect);
582 d->setFragmentShader(fileUrl);
583}
584
585/*!
586 \qmlproperty url QtQuick::ShaderEffect::vertexShader
587
588 This property contains a reference to a file with the preprocessed vertex
589 shader package, typically with an extension of \c{.qsb}. The value is
590 treated as a \l{QUrl}{URL}, similarly to other QML types, such as Image. It
591 must either be a local file or use the qrc scheme to access files embedded
592 via the Qt resource system. The URL may be absolute, or relative to the URL
593 of the component.
594
595 \sa fragmentShader
596*/
597
598QUrl QQuickShaderEffect::vertexShader() const
599{
600 Q_D(const QQuickShaderEffect);
601 return d->vertexShader();
602}
603
604void QQuickShaderEffect::setVertexShader(const QUrl &fileUrl)
605{
606 Q_D(QQuickShaderEffect);
607 d->setVertexShader(fileUrl);
608}
609
610/*!
611 \qmlproperty bool QtQuick::ShaderEffect::blending
612
613 If this property is true, the output from the \l fragmentShader is blended
614 with the background using source-over blend mode. If false, the background
615 is disregarded. Blending decreases the performance, so you should set this
616 property to false when blending is not needed. The default value is true.
617*/
618
619bool QQuickShaderEffect::blending() const
620{
621 Q_D(const QQuickShaderEffect);
622 return d->blending();
623}
624
625void QQuickShaderEffect::setBlending(bool enable)
626{
627 Q_D(QQuickShaderEffect);
628 d->setBlending(enable);
629}
630
631/*!
632 \qmlproperty variant QtQuick::ShaderEffect::mesh
633
634 This property defines the mesh used to draw the ShaderEffect. It can hold
635 any \l GridMesh object.
636 If a size value is assigned to this property, the ShaderEffect implicitly
637 uses a \l GridMesh with the value as
638 \l{GridMesh::resolution}{mesh resolution}. By default, this property is
639 the size 1x1.
640
641 \sa GridMesh
642*/
643
644QVariant QQuickShaderEffect::mesh() const
645{
646 Q_D(const QQuickShaderEffect);
647 return d->mesh();
648}
649
650void QQuickShaderEffect::setMesh(const QVariant &mesh)
651{
652 Q_D(QQuickShaderEffect);
653 d->setMesh(mesh);
654}
655
656/*!
657 \qmlproperty enumeration QtQuick::ShaderEffect::cullMode
658
659 This property defines which sides of the item should be visible.
660
661 \value ShaderEffect.NoCulling Both sides are visible
662 \value ShaderEffect.BackFaceCulling only the front side is visible
663 \value ShaderEffect.FrontFaceCulling only the back side is visible
664
665 The default is NoCulling.
666*/
667
668QQuickShaderEffect::CullMode QQuickShaderEffect::cullMode() const
669{
670 Q_D(const QQuickShaderEffect);
671 return d->cullMode();
672}
673
674void QQuickShaderEffect::setCullMode(CullMode face)
675{
676 Q_D(QQuickShaderEffect);
677 return d->setCullMode(face);
678}
679
680/*!
681 \qmlproperty bool QtQuick::ShaderEffect::supportsAtlasTextures
682
683 Set this property true to confirm that your shader code doesn't rely on
684 qt_MultiTexCoord0 ranging from (0,0) to (1,1) relative to the mesh.
685 In this case the range of qt_MultiTexCoord0 will rather be based on the position
686 of the texture within the atlas. This property currently has no effect if there
687 is less, or more, than one sampler uniform used as input to your shader.
688
689 This differs from providing qt_SubRect_<name> uniforms in that the latter allows
690 drawing one or more textures from the atlas in a single ShaderEffect item, while
691 supportsAtlasTextures allows multiple instances of a ShaderEffect component using
692 a different source image from the atlas to be batched in a single draw.
693 Both prevent a texture from being copied out of the atlas when referenced by a ShaderEffect.
694
695 The default value is false.
696
697 \since 5.4
698 \since QtQuick 2.4
699*/
700
701bool QQuickShaderEffect::supportsAtlasTextures() const
702{
703 Q_D(const QQuickShaderEffect);
704 return d->supportsAtlasTextures();
705}
706
707void QQuickShaderEffect::setSupportsAtlasTextures(bool supports)
708{
709 Q_D(QQuickShaderEffect);
710 d->setSupportsAtlasTextures(supports);
711}
712
713/*!
714 \qmlproperty enumeration QtQuick::ShaderEffect::status
715
716 This property tells the current status of the shaders.
717
718 \value ShaderEffect.Compiled the shader program was successfully compiled and linked.
719 \value ShaderEffect.Uncompiled the shader program has not yet been compiled.
720 \value ShaderEffect.Error the shader program failed to compile or link.
721
722 When setting the fragment or vertex shader source code, the status will
723 become Uncompiled. The first time the ShaderEffect is rendered with new
724 shader source code, the shaders are compiled and linked, and the status is
725 updated to Compiled or Error.
726
727 When runtime compilation is not in use and the shader properties refer to
728 files with bytecode, the status is always Compiled. The contents of the
729 shader is not examined (apart from basic reflection to discover vertex
730 input elements and constant buffer data) until later in the rendering
731 pipeline so potential errors (like layout or root signature mismatches)
732 will only be detected at a later point.
733
734 \sa log
735*/
736
737/*!
738 \qmlproperty string QtQuick::ShaderEffect::log
739
740 This property holds a log of warnings and errors from the latest attempt at
741 compiling the shaders. It is updated at the same time \l status is set to
742 Compiled or Error.
743
744 \note In Qt 6, the shader pipeline promotes compiling and translating the
745 Vulkan-style GLSL shaders offline, or at build time at latest. This does
746 not necessarily mean there is no shader compilation happening at run time,
747 but even if there is, ShaderEffect is not involved in that, and syntax and
748 similar errors should not occur anymore at that stage. Therefore the value
749 of this property is typically empty.
750
751 \sa status
752*/
753
754QString QQuickShaderEffect::log() const
755{
756 Q_D(const QQuickShaderEffect);
757 return d->log();
758}
759
760QQuickShaderEffect::Status QQuickShaderEffect::status() const
761{
762 Q_D(const QQuickShaderEffect);
763 return d->status();
764}
765
766bool QQuickShaderEffect::event(QEvent *e)
767{
768 Q_D(QQuickShaderEffect);
769 d->handleEvent(e);
770 return QQuickItem::event(e);
771}
772
773void QQuickShaderEffect::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
774{
775 Q_D(QQuickShaderEffect);
776 d->handleGeometryChanged(newGeometry, oldGeometry);
777 QQuickItem::geometryChange(newGeometry, oldGeometry);
778}
779
780QSGNode *QQuickShaderEffect::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData)
781{
782 Q_D(QQuickShaderEffect);
783 return d->handleUpdatePaintNode(oldNode, updatePaintNodeData);
784}
785
786void QQuickShaderEffect::componentComplete()
787{
788 Q_D(QQuickShaderEffect);
789 d->maybeUpdateShaders();
790 QQuickItem::componentComplete();
791}
792
793void QQuickShaderEffect::itemChange(ItemChange change, const ItemChangeData &value)
794{
795 Q_D(QQuickShaderEffect);
796 d->handleItemChange(change, value);
797 QQuickItem::itemChange(change, value);
798}
799
800bool QQuickShaderEffect::isComponentComplete() const
801{
802 return QQuickItem::isComponentComplete();
803}
804
805bool QQuickShaderEffect::updateUniformValue(const QByteArray &name, const QVariant &value)
806{
807 auto node = static_cast<QSGShaderEffectNode *>(QQuickItemPrivate::get(item: this)->paintNode);
808 if (!node)
809 return false;
810
811 Q_D(QQuickShaderEffect);
812 return d->updateUniformValue(name, value, node);
813}
814
815void QQuickShaderEffectPrivate::updatePolish()
816{
817 Q_Q(QQuickShaderEffect);
818 if (!qmlEngine(q))
819 return;
820 maybeUpdateShaders();
821}
822
823constexpr int indexToMappedId(const int shaderType, const int idx)
824{
825 return idx | (shaderType << 16);
826}
827
828constexpr int mappedIdToIndex(const int mappedId)
829{
830 return mappedId & 0xFFFF;
831}
832
833constexpr int mappedIdToShaderType(const int mappedId)
834{
835 return mappedId >> 16;
836}
837
838QQuickShaderEffectPrivate::QQuickShaderEffectPrivate()
839 : m_meshResolution(1, 1)
840 , m_mesh(nullptr)
841 , m_cullMode(QQuickShaderEffect::NoCulling)
842 , m_blending(true)
843 , m_supportsAtlasTextures(false)
844 , m_mgr(nullptr)
845 , m_fragNeedsUpdate(true)
846 , m_vertNeedsUpdate(true)
847{
848 qRegisterMetaType<QSGGuiThreadShaderEffectManager::ShaderInfo::Type>(typeName: "ShaderInfo::Type");
849 for (int i = 0; i < NShader; ++i)
850 m_inProgress[i] = nullptr;
851}
852
853QQuickShaderEffectPrivate::~QQuickShaderEffectPrivate()
854{
855 Q_ASSERT(m_mgr == nullptr);
856}
857
858void QQuickShaderEffectPrivate::setFragmentShader(const QUrl &fileUrl)
859{
860 Q_Q(QQuickShaderEffect);
861 if (m_fragShader == fileUrl)
862 return;
863
864 m_fragShader = fileUrl;
865
866 m_fragNeedsUpdate = true;
867 if (q->isComponentComplete())
868 maybeUpdateShaders();
869
870 emit q->fragmentShaderChanged();
871}
872
873void QQuickShaderEffectPrivate::setVertexShader(const QUrl &fileUrl)
874{
875 Q_Q(QQuickShaderEffect);
876 if (m_vertShader == fileUrl)
877 return;
878
879 m_vertShader = fileUrl;
880
881 m_vertNeedsUpdate = true;
882 if (q->isComponentComplete())
883 maybeUpdateShaders();
884
885 emit q->vertexShaderChanged();
886}
887
888void QQuickShaderEffectPrivate::setBlending(bool enable)
889{
890 Q_Q(QQuickShaderEffect);
891 if (m_blending == enable)
892 return;
893
894 m_blending = enable;
895 q->update();
896 emit q->blendingChanged();
897}
898
899QVariant QQuickShaderEffectPrivate::mesh() const
900{
901 return m_mesh ? QVariant::fromValue(value: static_cast<QObject *>(m_mesh))
902 : QVariant::fromValue(value: m_meshResolution);
903}
904
905void QQuickShaderEffectPrivate::setMesh(const QVariant &mesh)
906{
907 Q_Q(QQuickShaderEffect);
908 QQuickShaderEffectMesh *newMesh = qobject_cast<QQuickShaderEffectMesh *>(object: qvariant_cast<QObject *>(v: mesh));
909 if (newMesh && newMesh == m_mesh)
910 return;
911
912 if (m_mesh)
913 QObject::disconnect(m_meshConnection);
914
915 m_mesh = newMesh;
916
917 if (m_mesh) {
918 m_meshConnection = QObject::connect(sender: m_mesh, signal: &QQuickShaderEffectMesh::geometryChanged, context: q,
919 slot: [this] { markGeometryDirtyAndUpdate(); });
920 } else {
921 if (mesh.canConvert<QSize>()) {
922 m_meshResolution = mesh.toSize();
923 } else {
924 QList<QByteArray> res = mesh.toByteArray().split(sep: 'x');
925 bool ok = res.size() == 2;
926 if (ok) {
927 int w = res.at(i: 0).toInt(ok: &ok);
928 if (ok) {
929 int h = res.at(i: 1).toInt(ok: &ok);
930 if (ok)
931 m_meshResolution = QSize(w, h);
932 }
933 }
934 if (!ok)
935 qWarning(msg: "ShaderEffect: mesh property must be a size or an object deriving from QQuickShaderEffectMesh");
936 }
937 m_defaultMesh.setResolution(m_meshResolution);
938 }
939
940 m_dirty |= QSGShaderEffectNode::DirtyShaderMesh;
941 q->update();
942
943 emit q->meshChanged();
944}
945
946void QQuickShaderEffectPrivate::setCullMode(QQuickShaderEffect::CullMode face)
947{
948 Q_Q(QQuickShaderEffect);
949 if (m_cullMode == face)
950 return;
951
952 m_cullMode = face;
953 q->update();
954 emit q->cullModeChanged();
955}
956
957void QQuickShaderEffectPrivate::setSupportsAtlasTextures(bool supports)
958{
959 Q_Q(QQuickShaderEffect);
960 if (m_supportsAtlasTextures == supports)
961 return;
962
963 m_supportsAtlasTextures = supports;
964 markGeometryDirtyAndUpdate();
965 emit q->supportsAtlasTexturesChanged();
966}
967
968QString QQuickShaderEffectPrivate::parseLog()
969{
970 maybeUpdateShaders();
971 return log();
972}
973
974QString QQuickShaderEffectPrivate::log() const
975{
976 QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager();
977 if (!mgr)
978 return QString();
979
980 return mgr->log();
981}
982
983QQuickShaderEffect::Status QQuickShaderEffectPrivate::status() const
984{
985 QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager();
986 if (!mgr)
987 return QQuickShaderEffect::Uncompiled;
988
989 return QQuickShaderEffect::Status(mgr->status());
990}
991
992void QQuickShaderEffectPrivate::handleEvent(QEvent *event)
993{
994 if (event->type() == QEvent::DynamicPropertyChange) {
995 const auto propertyName = static_cast<QDynamicPropertyChangeEvent *>(event)->propertyName();
996 for (int i = 0; i < NShader; ++i) {
997 const auto mappedId = findMappedShaderVariableId(name: propertyName, shaderType: Shader(i));
998 if (mappedId)
999 propertyChanged(mappedId: *mappedId);
1000 }
1001 }
1002}
1003
1004void QQuickShaderEffectPrivate::handleGeometryChanged(const QRectF &, const QRectF &)
1005{
1006 m_dirty |= QSGShaderEffectNode::DirtyShaderGeometry;
1007}
1008
1009QSGNode *QQuickShaderEffectPrivate::handleUpdatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
1010{
1011 Q_Q(QQuickShaderEffect);
1012 QSGShaderEffectNode *node = static_cast<QSGShaderEffectNode *>(oldNode);
1013
1014 if (q->width() <= 0 || q->height() <= 0) {
1015 delete node;
1016 return nullptr;
1017 }
1018
1019 // Do not change anything while a new shader is being reflected or compiled.
1020 if (m_inProgress[Vertex] || m_inProgress[Fragment])
1021 return node;
1022
1023 // The manager should be already created on the gui thread. Just take that instance.
1024 QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager();
1025 if (!mgr) {
1026 delete node;
1027 return nullptr;
1028 }
1029
1030 if (!node) {
1031 QSGRenderContext *rc = QQuickWindowPrivate::get(c: q->window())->context;
1032 node = rc->sceneGraphContext()->createShaderEffectNode(renderContext: rc);
1033 if (!node) {
1034 qWarning(msg: "No shader effect node");
1035 return nullptr;
1036 }
1037 m_dirty = QSGShaderEffectNode::DirtyShaderAll;
1038 QObject::connect(sender: node, signal: &QSGShaderEffectNode::textureChanged, context: q, slot: [this] { markGeometryDirtyAndUpdateIfSupportsAtlas(); });
1039 }
1040
1041 QSGShaderEffectNode::SyncData sd;
1042 sd.dirty = m_dirty;
1043 sd.cullMode = QSGShaderEffectNode::CullMode(m_cullMode);
1044 sd.blending = m_blending;
1045 sd.vertex.shader = &m_shaders[Vertex];
1046 sd.vertex.dirtyConstants = &m_dirtyConstants[Vertex];
1047 sd.vertex.dirtyTextures = &m_dirtyTextures[Vertex];
1048 sd.fragment.shader = &m_shaders[Fragment];
1049 sd.fragment.dirtyConstants = &m_dirtyConstants[Fragment];
1050 sd.fragment.dirtyTextures = &m_dirtyTextures[Fragment];
1051 sd.materialTypeCacheKey = q->window();
1052 sd.viewCount = QQuickWindowPrivate::get(c: q->window())->multiViewCount();
1053
1054 node->syncMaterial(syncData: &sd);
1055
1056 if (m_dirty & QSGShaderEffectNode::DirtyShaderMesh) {
1057 node->setGeometry(nullptr);
1058 m_dirty &= ~QSGShaderEffectNode::DirtyShaderMesh;
1059 m_dirty |= QSGShaderEffectNode::DirtyShaderGeometry;
1060 }
1061
1062 if (m_dirty & QSGShaderEffectNode::DirtyShaderGeometry) {
1063 const QRectF rect(0, 0, q->width(), q->height());
1064 QQuickShaderEffectMesh *mesh = m_mesh ? m_mesh : &m_defaultMesh;
1065 QSGGeometry *geometry = node->geometry();
1066
1067 const QRectF srcRect = node->updateNormalizedTextureSubRect(supportsAtlasTextures: m_supportsAtlasTextures);
1068 geometry = mesh->updateGeometry(geometry, attrCount: 2, posIndex: 0, srcRect, rect);
1069
1070 node->setFlag(QSGNode::OwnsGeometry, false);
1071 node->setGeometry(geometry);
1072 node->setFlag(QSGNode::OwnsGeometry, true);
1073
1074 m_dirty &= ~QSGShaderEffectNode::DirtyShaderGeometry;
1075 }
1076
1077 m_dirty = {};
1078 for (int i = 0; i < NShader; ++i) {
1079 m_dirtyConstants[i].clear();
1080 m_dirtyTextures[i].clear();
1081 }
1082
1083 return node;
1084}
1085
1086void QQuickShaderEffectPrivate::maybeUpdateShaders()
1087{
1088 Q_Q(QQuickShaderEffect);
1089 if (m_vertNeedsUpdate)
1090 m_vertNeedsUpdate = !updateShader(shaderType: Vertex, fileUrl: m_vertShader);
1091 if (m_fragNeedsUpdate)
1092 m_fragNeedsUpdate = !updateShader(shaderType: Fragment, fileUrl: m_fragShader);
1093 if (m_vertNeedsUpdate || m_fragNeedsUpdate) {
1094 // This function is invoked either from componentComplete or in a
1095 // response to a previous invocation's polish() request. If this is
1096 // case #1 then updateShader can fail due to not having a window or
1097 // scenegraph ready. Schedule the polish to try again later. In case #2
1098 // the backend probably does not have shadereffect support so there is
1099 // nothing to do for us here.
1100 if (!q->window() || !q->window()->isSceneGraphInitialized())
1101 q->polish();
1102 }
1103}
1104
1105bool QQuickShaderEffectPrivate::updateUniformValue(const QByteArray &name, const QVariant &value,
1106 QSGShaderEffectNode *node)
1107{
1108 Q_Q(QQuickShaderEffect);
1109 const auto mappedId = findMappedShaderVariableId(name);
1110 if (!mappedId)
1111 return false;
1112
1113 const Shader type = Shader(mappedIdToShaderType(mappedId: *mappedId));
1114 const int idx = mappedIdToIndex(mappedId: *mappedId);
1115
1116 // Update value
1117 m_shaders[type].varData[idx].value = value;
1118
1119 // Insert dirty uniform
1120 QSet<int> dirtyConstants[NShader];
1121 dirtyConstants[type].insert(value: idx);
1122
1123 // Sync material change
1124 QSGShaderEffectNode::SyncData sd;
1125 sd.dirty = QSGShaderEffectNode::DirtyShaderConstant;
1126 sd.cullMode = QSGShaderEffectNode::CullMode(m_cullMode);
1127 sd.blending = m_blending;
1128 sd.vertex.shader = &m_shaders[Vertex];
1129 sd.vertex.dirtyConstants = &dirtyConstants[Vertex];
1130 sd.vertex.dirtyTextures = {};
1131 sd.fragment.shader = &m_shaders[Fragment];
1132 sd.fragment.dirtyConstants = &dirtyConstants[Fragment];
1133 sd.fragment.dirtyTextures = {};
1134 sd.materialTypeCacheKey = q->window();
1135 sd.viewCount = QQuickWindowPrivate::get(c: q->window())->multiViewCount();
1136
1137 node->syncMaterial(syncData: &sd);
1138
1139 return true;
1140}
1141
1142void QQuickShaderEffectPrivate::handleItemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
1143{
1144 if (inDestructor)
1145 return;
1146
1147 // Move the window ref.
1148 if (change == QQuickItem::ItemSceneChange) {
1149 for (int shaderType = 0; shaderType < NShader; ++shaderType) {
1150 for (const auto &vd : std::as_const(t&: m_shaders[shaderType].varData)) {
1151 if (vd.specialType == QSGShaderEffectNode::VariableData::Source) {
1152 QQuickItem *source = qobject_cast<QQuickItem *>(o: qvariant_cast<QObject *>(v: vd.value));
1153 if (source) {
1154 if (value.window)
1155 QQuickItemPrivate::get(item: source)->refWindow(value.window);
1156 else
1157 QQuickItemPrivate::get(item: source)->derefWindow();
1158 }
1159 }
1160 }
1161 }
1162 }
1163}
1164
1165QSGGuiThreadShaderEffectManager *QQuickShaderEffectPrivate::shaderEffectManager() const
1166{
1167 Q_Q(const QQuickShaderEffect);
1168 if (!m_mgr) {
1169 // return null if this is not the gui thread and not already created
1170 if (QThread::currentThread() != q->thread())
1171 return m_mgr;
1172 QQuickWindow *w = q->window();
1173 if (w) { // note: just the window, don't care about isSceneGraphInitialized() here
1174 m_mgr = QQuickWindowPrivate::get(c: w)->context->sceneGraphContext()->createGuiThreadShaderEffectManager();
1175 if (m_mgr) {
1176 QObject::connect(sender: m_mgr, signal: &QSGGuiThreadShaderEffectManager::logAndStatusChanged, context: q, slot: &QQuickShaderEffect::logChanged);
1177 QObject::connect(sender: m_mgr, signal: &QSGGuiThreadShaderEffectManager::logAndStatusChanged, context: q, slot: &QQuickShaderEffect::statusChanged);
1178 QObject::connect(sender: m_mgr, signal: &QSGGuiThreadShaderEffectManager::shaderCodePrepared, context: q,
1179 slot: [this](bool ok, QSGGuiThreadShaderEffectManager::ShaderInfo::Type typeHint,
1180 const QUrl &loadUrl, QSGGuiThreadShaderEffectManager::ShaderInfo *result)
1181 { const_cast<QQuickShaderEffectPrivate *>(this)->shaderCodePrepared(ok, typeHint, loadUrl, result); });
1182 }
1183 }
1184 }
1185 return m_mgr;
1186}
1187
1188void QQuickShaderEffectPrivate::disconnectSignals(Shader shaderType)
1189{
1190 Q_Q(QQuickShaderEffect);
1191 for (auto *mapper : m_mappers[shaderType]) {
1192 void *a = mapper;
1193 if (mapper)
1194 QObjectPrivate::disconnect(sender: q, signal_index: mapper->signalIndex(), slot: &a);
1195 }
1196 for (const auto &vd : std::as_const(t&: m_shaders[shaderType].varData)) {
1197 if (vd.specialType == QSGShaderEffectNode::VariableData::Source) {
1198 QQuickItem *source = qobject_cast<QQuickItem *>(o: qvariant_cast<QObject *>(v: vd.value));
1199 if (source) {
1200 if (q->window())
1201 QQuickItemPrivate::get(item: source)->derefWindow();
1202 auto it = m_destroyedConnections.constFind(key: source);
1203 if (it != m_destroyedConnections.constEnd()) {
1204 QObject::disconnect(*it);
1205 m_destroyedConnections.erase(it);
1206 }
1207 }
1208 }
1209 }
1210}
1211
1212void QQuickShaderEffectPrivate::clearMappers(QQuickShaderEffectPrivate::Shader shaderType)
1213{
1214 for (auto *mapper : std::as_const(t&: m_mappers[shaderType])) {
1215 if (mapper)
1216 mapper->destroyIfLastRef();
1217 }
1218 m_mappers[shaderType].clear();
1219}
1220
1221static inline QVariant getValueFromProperty(QObject *item, const QMetaObject *itemMetaObject,
1222 const QByteArray &name, int propertyIndex)
1223{
1224 QVariant value;
1225 if (propertyIndex == -1) {
1226 value = item->property(name);
1227 } else {
1228 value = itemMetaObject->property(index: propertyIndex).read(obj: item);
1229 }
1230 return value;
1231}
1232
1233using QQuickShaderInfoCache = QHash<QUrl, QSGGuiThreadShaderEffectManager::ShaderInfo>;
1234Q_GLOBAL_STATIC(QQuickShaderInfoCache, shaderInfoCache)
1235
1236void qtquick_shadereffect_purge_gui_thread_shader_cache()
1237{
1238 shaderInfoCache()->clear();
1239}
1240
1241bool QQuickShaderEffectPrivate::updateShader(Shader shaderType, const QUrl &fileUrl)
1242{
1243 Q_Q(QQuickShaderEffect);
1244 QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager();
1245 if (!mgr)
1246 return false;
1247
1248 const bool texturesSeparate = mgr->hasSeparateSamplerAndTextureObjects();
1249
1250 disconnectSignals(shaderType);
1251
1252 m_shaders[shaderType].shaderInfo.variables.clear();
1253 m_shaders[shaderType].varData.clear();
1254
1255 if (!fileUrl.isEmpty()) {
1256 const QQmlContext *context = qmlContext(q);
1257 const QUrl loadUrl = context ? context->resolvedUrl(fileUrl) : fileUrl;
1258 auto it = shaderInfoCache()->constFind(key: loadUrl);
1259 if (it != shaderInfoCache()->cend()) {
1260 m_shaders[shaderType].shaderInfo = *it;
1261 m_shaders[shaderType].hasShaderCode = true;
1262 } else {
1263 // Each prepareShaderCode call needs its own work area, hence the
1264 // dynamic alloc. If there are calls in progress, let those run to
1265 // finish, their results can then simply be ignored because
1266 // m_inProgress indicates what we care about.
1267 m_inProgress[shaderType] = new QSGGuiThreadShaderEffectManager::ShaderInfo;
1268 const QSGGuiThreadShaderEffectManager::ShaderInfo::Type typeHint =
1269 shaderType == Vertex ? QSGGuiThreadShaderEffectManager::ShaderInfo::TypeVertex
1270 : QSGGuiThreadShaderEffectManager::ShaderInfo::TypeFragment;
1271 // Figure out what input parameters and variables are used in the
1272 // shader. This is where the data is pulled in from the file.
1273 // (however, if there is compilation involved, that happens at a
1274 // later stage, up to the QRhi backend)
1275 mgr->prepareShaderCode(typeHint, src: loadUrl, result: m_inProgress[shaderType]);
1276 // the rest is handled in shaderCodePrepared()
1277 return true;
1278 }
1279 } else {
1280 m_shaders[shaderType].hasShaderCode = false;
1281 if (shaderType == Fragment) {
1282 // With built-in shaders hasShaderCode is set to false and all
1283 // metadata is empty, as it is left up to the node to provide a
1284 // built-in default shader and its metadata. However, in case of
1285 // the built-in fragment shader the value for 'source' has to be
1286 // provided and monitored like with an application-provided shader.
1287 QSGGuiThreadShaderEffectManager::ShaderInfo::Variable v;
1288 v.name = QByteArrayLiteral("source");
1289 v.bindPoint = 1; // fake, must match the default source bindPoint in qquickshadereffectnode.cpp
1290 v.type = texturesSeparate ? QSGGuiThreadShaderEffectManager::ShaderInfo::Texture
1291 : QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler;
1292 m_shaders[shaderType].shaderInfo.variables.append(t: v);
1293 }
1294 }
1295
1296 updateShaderVars(shaderType);
1297 m_dirty |= QSGShaderEffectNode::DirtyShaders;
1298 q->update();
1299 return true;
1300}
1301
1302void QQuickShaderEffectPrivate::shaderCodePrepared(bool ok, QSGGuiThreadShaderEffectManager::ShaderInfo::Type typeHint,
1303 const QUrl &loadUrl, QSGGuiThreadShaderEffectManager::ShaderInfo *result)
1304{
1305 Q_Q(QQuickShaderEffect);
1306 const Shader shaderType = typeHint == QSGGuiThreadShaderEffectManager::ShaderInfo::TypeVertex ? Vertex : Fragment;
1307
1308 // If another call was made to updateShader() for the same shader type in
1309 // the meantime then our results are useless, just drop them.
1310 if (result != m_inProgress[shaderType]) {
1311 delete result;
1312 return;
1313 }
1314
1315 m_shaders[shaderType].shaderInfo = *result;
1316 delete result;
1317 m_inProgress[shaderType] = nullptr;
1318
1319 if (!ok) {
1320 qWarning(msg: "ShaderEffect: shader preparation failed for %s\n%s\n",
1321 qPrintable(loadUrl.toString()), qPrintable(log()));
1322 m_shaders[shaderType].hasShaderCode = false;
1323 return;
1324 }
1325
1326 m_shaders[shaderType].hasShaderCode = true;
1327 shaderInfoCache()->insert(key: loadUrl, value: m_shaders[shaderType].shaderInfo);
1328 updateShaderVars(shaderType);
1329 m_dirty |= QSGShaderEffectNode::DirtyShaders;
1330 q->update();
1331}
1332
1333void QQuickShaderEffectPrivate::updateShaderVars(Shader shaderType)
1334{
1335 Q_Q(QQuickShaderEffect);
1336 QSGGuiThreadShaderEffectManager *mgr = shaderEffectManager();
1337 if (!mgr)
1338 return;
1339
1340 const bool texturesSeparate = mgr->hasSeparateSamplerAndTextureObjects();
1341
1342 const int varCount = m_shaders[shaderType].shaderInfo.variables.size();
1343 m_shaders[shaderType].varData.resize(size: varCount);
1344
1345 // Recreate signal mappers when the shader has changed.
1346 clearMappers(shaderType);
1347
1348 QQmlPropertyCache::ConstPtr propCache = QQmlData::ensurePropertyCache(object: q);
1349
1350 if (!m_itemMetaObject)
1351 m_itemMetaObject = q->metaObject();
1352
1353 // Hook up the signals to get notified about changes for properties that
1354 // correspond to variables in the shader. Store also the values.
1355 for (int i = 0; i < varCount; ++i) {
1356 const auto &v(m_shaders[shaderType].shaderInfo.variables.at(i));
1357 QSGShaderEffectNode::VariableData &vd(m_shaders[shaderType].varData[i]);
1358 const bool isSpecial = v.name.startsWith(bv: "qt_"); // special names not mapped to properties
1359 if (isSpecial) {
1360 if (v.name == "qt_Opacity")
1361 vd.specialType = QSGShaderEffectNode::VariableData::Opacity;
1362 else if (v.name == "qt_Matrix")
1363 vd.specialType = QSGShaderEffectNode::VariableData::Matrix;
1364 else if (v.name.startsWith(bv: "qt_SubRect_"))
1365 vd.specialType = QSGShaderEffectNode::VariableData::SubRect;
1366 continue;
1367 }
1368
1369 // The value of a property corresponding to a sampler is the source
1370 // item ref, unless there are separate texture objects in which case
1371 // the sampler is ignored (here).
1372 if (v.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Sampler) {
1373 if (texturesSeparate) {
1374 vd.specialType = QSGShaderEffectNode::VariableData::Unused;
1375 continue;
1376 } else {
1377 vd.specialType = QSGShaderEffectNode::VariableData::Source;
1378 }
1379 } else if (v.type == QSGGuiThreadShaderEffectManager::ShaderInfo::Texture) {
1380 Q_ASSERT(texturesSeparate);
1381 vd.specialType = QSGShaderEffectNode::VariableData::Source;
1382 } else {
1383 vd.specialType = QSGShaderEffectNode::VariableData::None;
1384 }
1385
1386 // Find the property on the ShaderEffect item.
1387 int propIdx = -1;
1388 const QQmlPropertyData *pd = nullptr;
1389 if (propCache) {
1390 pd = propCache->property(key: QLatin1String(v.name), object: nullptr, context: nullptr);
1391 if (pd) {
1392 if (!pd->isFunction())
1393 propIdx = pd->coreIndex();
1394 }
1395 }
1396 if (propIdx >= 0) {
1397 if (pd && !pd->isFunction()) {
1398 if (pd->notifyIndex() == -1) {
1399 qWarning(msg: "QQuickShaderEffect: property '%s' does not have notification method!", v.name.constData());
1400 } else {
1401 const int mappedId = indexToMappedId(shaderType, idx: i);
1402 auto mapper = new QtPrivate::EffectSlotMapper([this, mappedId](){
1403 this->propertyChanged(mappedId);
1404 });
1405 m_mappers[shaderType].append(t: mapper);
1406 mapper->setSignalIndex(m_itemMetaObject->property(index: propIdx).notifySignal().methodIndex());
1407 Q_ASSERT(q->metaObject() == m_itemMetaObject);
1408 bool ok = QObjectPrivate::connectImpl(sender: q, signal_index: pd->notifyIndex(), receiver: q, slot: nullptr, slotObj: mapper,
1409 type: Qt::AutoConnection, types: nullptr, senderMetaObject: m_itemMetaObject);
1410 if (!ok)
1411 qWarning() << "Failed to connect to property" << m_itemMetaObject->property(index: propIdx).name()
1412 << "(" << propIdx << ", signal index" << pd->notifyIndex()
1413 << ") of item" << q;
1414 }
1415 }
1416 } else {
1417 // Do not warn for dynamic properties.
1418 if (!q->property(name: v.name.constData()).isValid())
1419 qWarning(msg: "ShaderEffect: '%s' does not have a matching property", v.name.constData());
1420 }
1421
1422
1423 vd.propertyIndex = propIdx;
1424 vd.value = getValueFromProperty(item: q, itemMetaObject: m_itemMetaObject, name: v.name, propertyIndex: vd.propertyIndex);
1425 if (vd.specialType == QSGShaderEffectNode::VariableData::Source) {
1426 QQuickItem *source = qobject_cast<QQuickItem *>(o: qvariant_cast<QObject *>(v: vd.value));
1427 if (source) {
1428 if (q->window())
1429 QQuickItemPrivate::get(item: source)->refWindow(q->window());
1430
1431 // Cannot just pass q as the 'context' for the connect(). The
1432 // order of destruction is...complicated. Having an inline
1433 // source (e.g. source: ShaderEffectSource { ... } in QML would
1434 // emit destroyed() after the connection was already gone. To
1435 // work that around, store the Connection and manually
1436 // disconnect instead.
1437 if (!m_destroyedConnections.contains(key: source))
1438 m_destroyedConnections.insert(key: source, value: QObject::connect(sender: source, signal: &QObject::destroyed, slot: [this](QObject *obj) { sourceDestroyed(object: obj); }));
1439 }
1440 }
1441 }
1442}
1443
1444std::optional<int> QQuickShaderEffectPrivate::findMappedShaderVariableId(const QByteArray &name) const
1445{
1446 for (int shaderType = 0; shaderType < NShader; ++shaderType) {
1447 const auto &vars = m_shaders[shaderType].shaderInfo.variables;
1448 for (int idx = 0; idx < vars.size(); ++idx) {
1449 if (vars[idx].name == name)
1450 return indexToMappedId(shaderType, idx);
1451 }
1452 }
1453
1454 return {};
1455}
1456
1457std::optional<int> QQuickShaderEffectPrivate::findMappedShaderVariableId(const QByteArray &name, Shader shaderType) const
1458{
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 return {};
1466}
1467
1468bool QQuickShaderEffectPrivate::sourceIsUnique(QQuickItem *source, Shader typeToSkip, int indexToSkip) const
1469{
1470 for (int shaderType = 0; shaderType < NShader; ++shaderType) {
1471 for (int idx = 0; idx < m_shaders[shaderType].varData.size(); ++idx) {
1472 if (shaderType != typeToSkip || idx != indexToSkip) {
1473 const auto &vd(m_shaders[shaderType].varData[idx]);
1474 if (vd.specialType == QSGShaderEffectNode::VariableData::Source && qvariant_cast<QObject *>(v: vd.value) == source)
1475 return false;
1476 }
1477 }
1478 }
1479 return true;
1480}
1481
1482void QQuickShaderEffectPrivate::propertyChanged(int mappedId)
1483{
1484 Q_Q(QQuickShaderEffect);
1485 const Shader type = Shader(mappedIdToShaderType(mappedId));
1486 const int idx = mappedIdToIndex(mappedId);
1487 const auto &v(m_shaders[type].shaderInfo.variables[idx]);
1488 auto &vd(m_shaders[type].varData[idx]);
1489
1490 QVariant oldValue = vd.value;
1491 vd.value = getValueFromProperty(item: q, itemMetaObject: m_itemMetaObject, name: v.name, propertyIndex: vd.propertyIndex);
1492
1493 if (vd.specialType == QSGShaderEffectNode::VariableData::Source) {
1494 QQuickItem *source = qobject_cast<QQuickItem *>(o: qvariant_cast<QObject *>(v: oldValue));
1495 if (source) {
1496 if (q->window())
1497 QQuickItemPrivate::get(item: source)->derefWindow();
1498 // If the same source has been attached to two separate
1499 // textures/samplers, then changing one of them would trigger both
1500 // to be disconnected. So check first.
1501 if (sourceIsUnique(source, typeToSkip: type, indexToSkip: idx)) {
1502 auto it = m_destroyedConnections.constFind(key: source);
1503 if (it != m_destroyedConnections.constEnd()) {
1504 QObject::disconnect(*it);
1505 m_destroyedConnections.erase(it);
1506 }
1507 }
1508 }
1509
1510 source = qobject_cast<QQuickItem *>(o: qvariant_cast<QObject *>(v: vd.value));
1511 if (source) {
1512 // 'source' needs a window to get a scene graph node. It usually gets one through its
1513 // parent, but if the source item is "inline" rather than a reference -- i.e.
1514 // "property variant source: Image { }" instead of "property variant source: foo" -- it
1515 // will not get a parent. In those cases, 'source' should get the window from 'item'.
1516 if (q->window())
1517 QQuickItemPrivate::get(item: source)->refWindow(q->window());
1518 if (!m_destroyedConnections.contains(key: source))
1519 m_destroyedConnections.insert(key: source, value: QObject::connect(sender: source, signal: &QObject::destroyed, slot: [this](QObject *obj) { sourceDestroyed(object: obj); }));
1520 }
1521
1522 m_dirty |= QSGShaderEffectNode::DirtyShaderTexture;
1523 m_dirtyTextures[type].insert(value: idx);
1524
1525 } else {
1526 m_dirty |= QSGShaderEffectNode::DirtyShaderConstant;
1527 m_dirtyConstants[type].insert(value: idx);
1528 }
1529
1530 q->update();
1531}
1532
1533void QQuickShaderEffectPrivate::sourceDestroyed(QObject *object)
1534{
1535 for (int shaderType = 0; shaderType < NShader; ++shaderType) {
1536 for (auto &vd : m_shaders[shaderType].varData) {
1537 if (vd.specialType == QSGShaderEffectNode::VariableData::Source && vd.value.canConvert<QObject *>()) {
1538 if (qvariant_cast<QObject *>(v: vd.value) == object)
1539 vd.value = QVariant();
1540 }
1541 }
1542 }
1543}
1544
1545void QQuickShaderEffectPrivate::markGeometryDirtyAndUpdate()
1546{
1547 Q_Q(QQuickShaderEffect);
1548 m_dirty |= QSGShaderEffectNode::DirtyShaderGeometry;
1549 q->update();
1550}
1551
1552void QQuickShaderEffectPrivate::markGeometryDirtyAndUpdateIfSupportsAtlas()
1553{
1554 if (m_supportsAtlasTextures)
1555 markGeometryDirtyAndUpdate();
1556}
1557
1558QT_END_NAMESPACE
1559
1560#include "moc_qquickshadereffect_p.cpp"
1561

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