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