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

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