1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquick3deffect_p.h"
5
6#include <QtQuick3DRuntimeRender/private/qssgrendercontextcore_p.h>
7#include <QtQuick3DRuntimeRender/private/qssgrendereffect_p.h>
8#include <QtQuick3DRuntimeRender/private/qssgshadermaterialadapter_p.h>
9#include <QtQuick3DUtils/private/qssgutils_p.h>
10#include <QtQuick/qquickwindow.h>
11#include <QtQuick3D/private/qquick3dobject_p.h>
12#include <QtQuick3D/private/qquick3dscenemanager_p.h>
13#include <QtCore/qfile.h>
14#include <QtCore/qurl.h>
15
16
17QT_BEGIN_NAMESPACE
18
19/*!
20 \qmltype Effect
21 \inherits Object3D
22 \inqmlmodule QtQuick3D
23 \instantiates QQuick3DEffect
24 \brief Base component for creating a post-processing effect.
25
26 The Effect type allows the user to implement their own post-processing
27 effects for QtQuick3D.
28
29 \section1 Post-processing effects
30
31 A post-processing effect is conceptually very similar to Qt Quick's \l
32 ShaderEffect item. When an effect is present, the scene is rendered into a
33 separate texture first. The effect is then applied by drawing a textured
34 quad to the main render target, depending on the
35 \l{View3D::renderMode}{render mode} of the View3D. The effect can provide a
36 vertex shader, a fragment shader, or both. Effects are always applied on the
37 entire scene, per View3D.
38
39 Effects are associated with the \l SceneEnvironment in the
40 \l{SceneEnvironment::effects} property. The property is a list: effects can
41 be chained together; they are applied in the order they are in the list,
42 using the previous step's output as the input to the next one, with the last
43 effect's output defining the contents of the View3D.
44
45 \note \l SceneEnvironment and \l ExtendedSceneEnvironment provide a set of
46 built-in effects, such as depth of field, glow/bloom, lens flare, color
47 grading, and vignette. Always consider first if these are sufficient for
48 the application's needs, and prefer using the built-in facilities instead
49 of implementing a custom post-processing effect.
50
51 Effects are similar to \l{CustomMaterial}{custom materials} in many
52 ways. However, a custom material is associated with a model and is
53 responsible for the shading of that given mesh. Whereas an effect's vertex
54 shader always gets a quad (for example, two triangles) as its input, while
55 its fragment shader samples the texture with the scene's content.
56
57 Unlike custom materials, effects support multiple passes. For many effects
58 this it not necessary, and when there is a need to apply multiple effects,
59 identical results can often be achieved by chaining together multiple
60 effects in \l{SceneEnvironment::effects}{the SceneEnvironment}. This is
61 demonstrated by the \l{Qt Quick 3D - Custom Effect Example}{Custom Effect
62 example} as well. However, passes have the possibility to request additional
63 color buffers (texture), and specify which of these additional buffers they
64 output to. This allows implementing more complex image processing techniques
65 since subsequent passes can then use one or more of these additional
66 buffers, plus the original scene's content, as their input. If necessary,
67 these additional buffers can have an extended lifetime, meaning their
68 content is preserved between frames, which allows implementing effects that
69 rely on accumulating content from multiple frames, such as, motion blur.
70
71 When compared to Qt Quick's 2D ShaderEffect, the 3D post-processing effects
72 have the advantage of being able to work with depth buffer data, as well as
73 the ability to implement multiple passes with intermediate buffers. In
74 addition, the texture-related capabilities are extended: Qt Quick 3D allows
75 more fine-grained control over filtering modes, and allows effects to work
76 with texture formats other than RGBA8, for example, floating point formats.
77
78 \note Post-processing effects are currently available when the View3D
79 has its \l{View3D::renderMode}{renderMode} set to \c Offscreen,
80 \c Underlay or \c Overlay. Effects will not be rendered for \c Inline mode.
81
82 \note When using post-processing effects, the application-provided shaders
83 should expect linear color data without tonemapping applied. The
84 tonemapping that is performed during the main render pass (or during skybox
85 rendering, if there is a skybox) when
86 \l{SceneEnvironment::tonemapMode}{tonemapMode} is set to a value other than
87 \c SceneEnvironment.TonemapModeNone, is automatically disabled when there
88 is at least one post-processing effect specified in the SceneEnvironment.
89 The last effect in the chain (more precisely, the last pass of the last
90 effect in the chain) will automatically get its fragment shader amended to
91 perform the same tonemapping the main render pass would.
92
93 \note Effects that perform their own tonemapping should be used in a
94 SceneEnvironment that has the built-in tonemapping disabled by setting
95 \l{SceneEnvironment::tonemapMode}{tonemapMode} to \c
96 SceneEnvironment.TonemapModeNone.
97
98 \note By default the texture used as the effects' input is created with a
99 floating point texture format, such as 16-bit floating point RGBA. The
100 output texture's format is the same since by default it follows the input
101 format. This can be overridden using \l Buffer and an empty name. The
102 default RGBA16F is useful because it allows working with non-tonemapped
103 linear data without having the color values outside the 0-1 range clamped.
104
105 \section1 Exposing data to the shaders
106
107 Like with CustomMaterial or ShaderEffect, the dynamic properties of an
108 Effect object can be changed and animated using the usual QML and Qt Quick
109 facilities, and the values are exposed to the shaders automatically. The
110 following list shows how properties are mapped:
111
112 \list
113 \li bool, int, real -> bool, int, float
114 \li QColor, \l{QtQml::Qt::rgba()}{color} -> vec4, and the color gets
115 converted to linear, assuming sRGB space for the color value specified in
116 QML. The built-in Qt colors, such as \c{"green"} are in sRGB color space as
117 well, and the same conversion is performed for all color properties of
118 DefaultMaterial and PrincipledMaterial, so this behavior of Effect
119 matches those.
120 \li QRect, QRectF, \l{QtQml::Qt::rect()}{rect} -> vec4
121 \li QPoint, QPointF, \l{QtQml::Qt::point()}{point}, QSize, QSizeF, \l{QtQml::Qt::size()}{size} -> vec2
122 \li QVector2D, \l{QtQml::Qt::vector2d()}{vector2d} -> vec3
123 \li QVector3D, \l{QtQml::Qt::vector3d()}{vector3d} -> vec3
124 \li QVector4D, \l{QtQml::Qt::vector4d()}{vector4d} -> vec4
125 \li QMatrix4x4, \l{QtQml::Qt::matrix4x4()}{matrix4x4} -> mat4
126 \li QQuaternion, \l{QtQml::Qt::quaternion()}{quaternion} -> vec4, scalar value is \c w
127
128 \li TextureInput -> sampler2D or samplerCube, depending on whether \l
129 Texture or \l CubeMapTexture is used in the texture property of the
130 TextureInput. Setting the \l{TextureInput::enabled}{enabled} property to
131 false leads to exposing a dummy texture to the shader, meaning the shaders
132 are still functional but will sample a texture with opaque black image
133 content. Pay attention to the fact that properties for samplers must always
134 reference a \l TextureInput object, not a \l Texture directly. When it
135 comes to the \l Texture properties, the source, tiling, and filtering
136 related ones are the only ones that are taken into account implicitly with
137 effects, as the rest (such as, UV transformations) is up to the custom
138 shaders to implement as they see fit.
139
140 \endlist
141
142 \note When a uniform referenced in the shader code does not have a
143 corresponding property, it will cause a shader compilation error when
144 processing the effect at run time. There are some exceptions to this,
145 such as, sampler uniforms, that get a dummy texture bound when no
146 corresponding QML property is present, but as a general rule, all uniforms
147 and samplers must have a corresponding property declared in the
148 Effect object.
149
150 \section1 Getting started with user-defined effects
151
152 A custom post-processing effect involves at minimum an Effect object and a
153 fragment shader snippet. Some effects will also want a customized vertex
154 shader as well.
155
156 As a simple example, let's create an effect that combines the scene's
157 content with an image, while further altering the red channel's value in an
158 animated manner:
159
160 \table 70%
161 \row
162 \li \qml
163 Effect {
164 id: simpleEffect
165 property TextureInput tex: TextureInput {
166 texture: Texture { source: "image.png" }
167 }
168 property real redLevel
169 NumberAnimation on redLevel { from: 0; to: 1; duration: 5000; loops: -1 }
170 passes: Pass {
171 shaders: Shader {
172 stage: Shader.Fragment
173 shader: "effect.frag"
174 }
175 }
176 }
177 \endqml
178 \li \badcode
179 void MAIN()
180 {
181 vec4 c = texture(tex, TEXTURE_UV);
182 c.r *= redLevel;
183 FRAGCOLOR = c * texture(INPUT, INPUT_UV);
184 }
185 \endcode
186 \endtable
187
188 Here the texture with the image \c{image.png} is exposed to the shader under
189 the name \c tex. The value of redLevel is available in the shader in a \c
190 float uniform with the same name.
191
192 The fragment shader must contain a function called \c MAIN. The final
193 fragment color is determined by \c FRAGCOLOR. The main input texture, with
194 the contents of the View3D's scene, is accessible under a \c sampler2D with
195 the name \c INPUT. The UV coordinates from the quad are in \c
196 INPUT_UV. These UV values are always suitable for sampling \c INPUT,
197 regardless of the underlying graphics API at run time (and so regardless of
198 the Y axis direction in images since the necessary adjustments are applied
199 automatically by Qt Quick 3D). Sampling the texture with our external image
200 is done using \c TEXTURE_UV. \c INPUT_UV is not suitable in cross-platform
201 applications since V needs to be flipped to cater for the coordinate system
202 differences mentioned before, using a logic that is different for textures
203 based on images and textures used as render targets. Fortunately this is all
204 taken care of by the engine so the shader need no further logic for this.
205
206 Once simpleEffect is available, it can be associated with the effects list
207 of a the View3D's SceneEnvironment:
208
209 \qml
210 environment: SceneEnvironment {
211 effects: [ simpleEffect ]
212 }
213 \endqml
214
215 The results would look something like the following, with the original scene
216 on the left and with the effect applied on the right:
217
218 \table 70%
219 \row
220 \li \image effect_intro_1.png
221 \li \image effect_intro_2.png
222 \endtable
223
224 \note The \c shader property value in Shader is a URL, as is the custom in
225 QML and Qt Quick, referencing the file containing the shader snippet, and
226 works very similarly to ShaderEffect or
227 \l{Image::source}{Image.source}. Only the \c file and \c qrc schemes are
228 supported.. It is also possible to omit the \c file scheme, allowing to
229 specify a relative path in a convenient way. Such a path is resolved
230 relative to the component's (the \c{.qml} file's) location.
231
232 \note Shader code is always provided using Vulkan-style GLSL, regardless of
233 the graphics API used by Qt at run time.
234
235 \note The vertex and fragment shader code provided by the effect are not
236 full, complete GLSL shaders on their own. Rather, they provide a \c MAIN
237 function, and optionally a set of \c VARYING declarations, which are then
238 amended with further shader code by the engine.
239
240 \section1 Effects with vertex shaders
241
242 A vertex shader, when present, must provide a function called \c MAIN. In
243 the vast majority of cases the custom vertex shader will not want to provide
244 its own calculation of the homogenous vertex position, but it is possible
245 using \c POSITION, \c VERTEX, and \c MODELVIEWPROJECTION_MATRIX. When
246 \c POSITION is not present in the custom shader code, a statement equivalent to
247 \c{POSITION = MODELVIEWPROJECTION_MATRIX * vec4(VERTEX, 1.0);} will be
248 injected automatically by Qt Quick 3D.
249
250 To pass data between the vertex and fragment shaders, use the VARYING
251 keyword. Internally this will then be transformed into the appropriate
252 vertex output or fragment input declaration. The fragment shader can use the
253 same declaration, which then allows to read the interpolated value for the
254 current fragment.
255
256 Let's look at example, that is in effect very similar to the built-in
257 DistortionSpiral effect:
258
259 \table 70%
260 \row
261 \li \badcode
262 VARYING vec2 center_vec;
263 void MAIN()
264 {
265 center_vec = INPUT_UV - vec2(0.5, 0.5);
266 center_vec.y *= INPUT_SIZE.y / INPUT_SIZE.x;
267 }
268 \endcode
269 \li \badcode
270 VARYING vec2 center_vec;
271 void MAIN()
272 {
273 float radius = 0.25;
274 float dist_to_center = length(center_vec) / radius;
275 vec2 texcoord = INPUT_UV;
276 if (dist_to_center <= 1.0) {
277 float rotation_amount = (1.0 - dist_to_center) * (1.0 - dist_to_center);
278 float r = radians(360.0) * rotation_amount / 4.0;
279 mat2 rotation = mat2(cos(r), sin(r), -sin(r), cos(r));
280 texcoord = vec2(0.5, 0.5) + rotation * (INPUT_UV - vec2(0.5, 0.5));
281 }
282 FRAGCOLOR = texture(INPUT, texcoord);
283 }
284 \endcode
285 \endtable
286
287 The Effect object's \c passes list should now specify both the vertex and
288 fragment snippets:
289
290 \qml
291 passes: Pass {
292 shaders: [
293 Shader {
294 stage: Shader.Vertex
295 shader: "effect.vert"
296 },
297 Shader {
298 stage: Shader.Fragment
299 shader: "effect.frag"
300 }
301 ]
302 }
303 \endqml
304
305 The end result looks like the following:
306
307 \table 70%
308 \row
309 \li \image effect_intro_1.png
310 \li \image effect_intro_3.png
311 \endtable
312
313 \section1 Special keywords in effect shaders
314
315 \list
316
317 \li \c VARYING - Declares a vertex output or fragment input, depending on the type of the current shader.
318 \li \c MAIN - This function must always be present in an effect shader.
319 \li \c FRAGCOLOR - \c vec4 - The final fragment color; the output of the fragment shader. (fragment shader only)
320 \li \c POSITION - \c vec4 - The homogenous position calculated in the vertex shader. (vertex shader only)
321 \li \c MODELVIEWPROJECTION_MATRIX - \c mat4 - The transformation matrix for the screen quad.
322 \li \c VERTEX - \c vec3 - The vertices of the quad; the input to the vertex shader. (vertex shader only)
323
324 \li \c INPUT - \c sampler2D - The sampler for the input texture with the
325 scene rendered into it, unless a pass redirects its input via a BufferInput
326 object, in which case \c INPUT refers to the additional color buffer's
327 texture referenced by the BufferInput.
328
329 \li \c INPUT_UV - \c vec2 - UV coordinates for sampling \c INPUT.
330
331 \li \c TEXTURE_UV - \c vec2 - UV coordinates suitable for sampling a Texture
332 with contents loaded from an image file.
333
334 \li \c INPUT_SIZE - \c vec2 - The size of the \c INPUT texture, in pixels.
335
336 \li \c OUTPUT_SIZE - \c vec2 - The size of the output buffer, in
337 pixels. Often the same as \c INPUT_SIZE, unless the pass outputs to an extra
338 Buffer with a size multiplier on it.
339
340 \li \c FRAME - \c float - A frame counter, incremented after each frame in the View3D.
341
342 \li \c DEPTH_TEXTURE - \c sampler2D - A depth texture with the depth buffer
343 contents with the opaque objects in the scene. Like with CustomMaterial, the
344 presence of this keyword in the shader triggers generating the depth texture
345 automatically.
346
347 \endlist
348
349 \section1 Building multi-pass effects
350
351 A multi-pass effect often uses more than one set of shaders, and takes the
352 \l{Pass::output}{output} and \l{Pass::commands}{commands} properties into
353 use. Each entry in the passes list translates to a render pass drawing a
354 quad into the pass's output texture, while sampling the effect's input texture
355 and optionally other textures as well.
356
357 The typical outline of a multi-pass Effect can look like the following:
358
359 \qml
360 passes: [
361 Pass {
362 shaders: [
363 Shader {
364 stage: Shader.Vertex
365 shader: "pass1.vert"
366 },
367 Shader {
368 stage: Shader.Fragment
369 shader: "pass1.frag"
370 }
371 // This pass outputs to the intermediate texture described
372 // by the Buffer object.
373 output: intermediateColorBuffer
374 ],
375 },
376 Pass {
377 shaders: [
378 Shader {
379 stage: Shader.Vertex
380 shader: "pass2.vert"
381 },
382 Shader {
383 stage: Shader.Fragment
384 shader: "pass2.frag"
385 }
386 // The output of the last pass needs no redirection, it is
387 // the final result of the effect.
388 ],
389 commands: [
390 // This pass reads from the intermediate texture, meaning
391 // INPUT in the shader will refer to the texture associated
392 // with the Buffer.
393 BufferInput {
394 buffer: intermediateColorBuffer
395 }
396 ]
397 }
398 ]
399 \endqml
400
401 What is \c intermediateColorBuffer?
402
403 \qml
404 Buffer {
405 id: intermediateColorBuffer
406 name: "tempBuffer"
407 // format: Buffer.RGBA8
408 // textureFilterOperation: Buffer.Linear
409 // textureCoordOperation: Buffer.ClampToEdge
410 }
411 \endqml
412
413 The commented properties are not necessary if the desired values match the
414 defaults.
415
416 Internally the presence of this Buffer object and referencing it from the \c
417 output property of a Pass leads to creating a texture with a size matching
418 the View3D, and so the size of the implicit input and output textures. When
419 this is not desired, the \l{Buffer::sizeMultiplier}{sizeMultiplier} property
420 can be used to get an intermediate texture with a different size. This can
421 lead to the \c INPUT_SIZE and \c OUTPUT_SIZE uniforms in the shader having
422 different values.
423
424 By default the Effect cannot count on textures preserving their contents
425 between frames. When a new intermediate texture is created, it is cleared to
426 \c{vec4(0.0)}. Afterwards, the same texture can be reused for another
427 purpose. Therefore, effect passes should always write to the entire texture,
428 without making assumptions about their content at the start of the pass.
429 There is an exception to this: Buffer objects with
430 \l{Buffer::bufferFlags}{bufferFlags} set to Buffer.SceneLifetime. This
431 indicates that the texture is permanently associated with a pass of the
432 effect and it will not be reused for other purposes. The contents of such
433 color buffers is preserved between frames. This is typically used in a
434 ping-pong fashion in effects like motion blur: the first pass takes the
435 persistent buffer as its input, in addition to the effects main input
436 texture, outputting to another intermediate buffer, while the second pass
437 outputs to the persistent buffer. This way in the first frame the first pass
438 samples an empty (transparent) texture, whereas in subsequent frames it
439 samples the output of the second pass from the previous frame. A third pass
440 can then blend the effect's input and the second pass' output together.
441
442 The BufferInput command type is used to expose custom texture buffers to the
443 render pass.
444
445 For instance, to access \c someBuffer in the render pass shaders under
446 the name, \c mySampler, the following can be added to its command list:
447 \qml
448 BufferInput { buffer: someBuffer; sampler: "mySampler" }
449 \endqml
450
451 If the \c sampler name is not specified, \c INPUT will be used as default.
452
453 Buffers can be useful to share intermediate results between render passes.
454
455 To expose preloaded textures to the effect, TextureInput should be used instead.
456 These can be defined as properties of the Effect itself, and will automatically
457 be accessible to the shaders by their property names.
458 \qml
459 property TextureInput tex: TextureInput {
460 texture: Texture { source: "image.png" }
461 }
462 \endqml
463
464 Here \c tex is a valid sampler in all shaders of all the passes of the
465 effect.
466
467 When it comes to uniform values from properties, all passes in the Effect
468 read the same values in their shaders. If necessary it is possible to
469 override the value of a uniform just for a given pass. This is achieved by
470 adding the \l SetUniformValue command to the list of commands for the pass.
471
472 \note The \l{SetUniformValue::target}{target} of the pass-specific uniform
473 value setter can only refer to a name that is the name of a property of the
474 effect. It can override the value for a property's corresponding uniform,
475 but it cannot introduce new uniforms.
476
477 \section1 Performance considerations
478
479 Be aware of the increased resource usage and potentially reduced performance
480 when using post-processing effects. Just like with Qt Quick layers and
481 ShaderEffect, rendering the scene into a texture and then using that to
482 texture a quad is not a cheap operation, especially on low-end hardware with
483 limited fragment processing power. The amount of additional graphics memory
484 needed, as well as the increase in GPU load both depend on the size of the
485 View3D (which, on embedded devices without a windowing system, may often be
486 as big as the screen resolution). Multi-pass effects, as well as applying
487 multiple effects increase the resource and performance requirements further.
488
489 Therefore, it is highly advisable to ensure early on in the development
490 lifecycle that the targeted device and graphics stack is able to cope with
491 the effects included in the design of the 3D scene at the final product's
492 screen resolution.
493
494 While unavoidable with techniques that need it, \c DEPTH_TEXTURE implies an
495 additional rendering pass to generate the contents of that texture, which
496 can also present a hit on less capable hardware. Therefore, use \c
497 DEPTH_TEXTURE in the effect's shaders only when essential.
498
499 The complexity of the operations in the shaders is also important. Just like
500 with CustomMaterial, a sub-optimal fragment shader can easily lead to
501 reduced rendering performance.
502
503 Be cautious with \l{Buffer::sizeMultiplier}{sizeMultiplier in Buffer} when
504 values larger than 1 are involved. For example, a multiplier of 4 means
505 creating and then rendering to a texture that is 4 times the size of the
506 View3D. Just like with shadow maps and multi- or supersampling, the
507 increased resource and performance costs can quickly outweigh the benefits
508 from better quality on systems with limited GPU power.
509
510 \sa Shader, Pass, Buffer, BufferInput, {Qt Quick 3D - Custom Effect Example}
511*/
512
513/*!
514 \qmlproperty list Effect::passes
515 Contains a list of render \l {Pass}{passes} implemented by the effect.
516*/
517
518QQuick3DEffect::QQuick3DEffect(QQuick3DObject *parent)
519 : QQuick3DObject(*(new QQuick3DObjectPrivate(QQuick3DObjectPrivate::Type::Effect)), parent)
520{
521}
522
523QQmlListProperty<QQuick3DShaderUtilsRenderPass> QQuick3DEffect::passes()
524{
525 return QQmlListProperty<QQuick3DShaderUtilsRenderPass>(this,
526 nullptr,
527 QQuick3DEffect::qmlAppendPass,
528 QQuick3DEffect::qmlPassCount,
529 QQuick3DEffect::qmlPassAt,
530 QQuick3DEffect::qmlPassClear);
531}
532
533// Default vertex and fragment shader code that is used when no corresponding
534// Shader is present in the Effect. These go through the usual processing so
535// should use the user-facing builtins.
536
537static const char *default_effect_vertex_shader =
538 "void MAIN()\n"
539 "{\n"
540 "}\n";
541
542static const char *default_effect_fragment_shader =
543 "void MAIN()\n"
544 "{\n"
545 " FRAGCOLOR = texture(INPUT, INPUT_UV);\n"
546 "}\n";
547
548static inline void insertVertexMainArgs(QByteArray &snippet)
549{
550 static const char *argKey = "/*%QT_ARGS_MAIN%*/";
551 const int argKeyLen = int(strlen(s: argKey));
552 const int argKeyPos = snippet.indexOf(bv: argKey);
553 if (argKeyPos >= 0)
554 snippet = snippet.left(len: argKeyPos) + QByteArrayLiteral("inout vec3 VERTEX") + snippet.mid(index: argKeyPos + argKeyLen);
555}
556
557QSSGRenderGraphObject *QQuick3DEffect::updateSpatialNode(QSSGRenderGraphObject *node)
558{
559 using namespace QSSGShaderUtils;
560
561 const auto &renderContext = QQuick3DObjectPrivate::get(item: this)->sceneManager->wattached->rci();
562 if (!renderContext) {
563 qWarning(msg: "QQuick3DEffect: No render context interface?");
564 return nullptr;
565 }
566
567 QSSGRenderEffect *effectNode = static_cast<QSSGRenderEffect *>(node);
568 bool newBackendNode = false;
569 if (!effectNode) {
570 effectNode = new QSSGRenderEffect;
571 newBackendNode = true;
572 }
573
574 bool shadersMayChange = false;
575 if (m_dirtyAttributes & Dirty::EffectChainDirty)
576 shadersMayChange = true;
577
578 const bool fullUpdate = newBackendNode || effectNode->incompleteBuildTimeObject;
579
580 if (fullUpdate || shadersMayChange) {
581 markAllDirty();
582
583 QMetaMethod propertyDirtyMethod;
584 const int idx = metaObject()->indexOfSlot(slot: "onPropertyDirty()");
585 if (idx != -1)
586 propertyDirtyMethod = metaObject()->method(index: idx);
587
588 // Properties -> uniforms
589 QSSGShaderCustomMaterialAdapter::StringPairList uniforms;
590 const int propCount = metaObject()->propertyCount();
591 int propOffset = metaObject()->propertyOffset();
592
593 // Effect can have multilayered inheritance structure, so find the actual propOffset
594 const QMetaObject *superClass = metaObject()->superClass();
595 while (superClass && qstrcmp(str1: superClass->className(), str2: "QQuick3DEffect") != 0) {
596 propOffset = superClass->propertyOffset();
597 superClass = superClass->superClass();
598 }
599
600 using TextureInputProperty = QPair<QQuick3DShaderUtilsTextureInput *, const char *>;
601
602 QVector<TextureInputProperty> textureProperties; // We'll deal with these later
603 for (int i = propOffset; i != propCount; ++i) {
604 const QMetaProperty property = metaObject()->property(index: i);
605 if (Q_UNLIKELY(!property.isValid()))
606 continue;
607
608 const auto name = property.name();
609 QMetaType propType = property.metaType();
610 QVariant propValue = property.read(obj: this);
611 if (propType == QMetaType(QMetaType::QVariant))
612 propType = propValue.metaType();
613
614 if (propType.id() >= QMetaType::User) {
615 if (propType.id() == qMetaTypeId<QQuick3DShaderUtilsTextureInput *>()) {
616 if (QQuick3DShaderUtilsTextureInput *texture = property.read(obj: this).value<QQuick3DShaderUtilsTextureInput *>())
617 textureProperties.push_back(t: {texture, name});
618 }
619 } else if (propType == QMetaType(QMetaType::QObjectStar)) {
620 if (QQuick3DShaderUtilsTextureInput *texture = qobject_cast<QQuick3DShaderUtilsTextureInput *>(object: propValue.value<QObject *>()))
621 textureProperties.push_back(t: {texture, name});
622 } else {
623 const auto type = uniformType(type: propType);
624 if (type != QSSGRenderShaderValue::Unknown) {
625 uniforms.append(t: { uniformTypeName(type: propType), name });
626 effectNode->properties.push_back(t: { name, uniformTypeName(type: propType),
627 propValue, uniformType(type: propType), i});
628 // Track the property changes
629 if (fullUpdate) {
630 if (property.hasNotifySignal() && propertyDirtyMethod.isValid())
631 connect(sender: this, signal: property.notifySignal(), receiver: this, method: propertyDirtyMethod);
632 } // else already connected
633 } else {
634 // ### figure out how _not_ to warn when there are no dynamic
635 // properties defined (because warnings like Blah blah objectName etc. are not helpful)
636 //qWarning("No known uniform conversion found for effect property %s. Skipping", property.name());
637 }
638 }
639 }
640
641 const auto processTextureProperty = [&](QQuick3DShaderUtilsTextureInput &texture, const QByteArray &name) {
642 QSSGRenderEffect::TextureProperty texProp;
643 QQuick3DTexture *tex = texture.texture(); // may be null if the TextureInput has no 'texture' set
644 if (fullUpdate) {
645 connect(sender: &texture, signal: &QQuick3DShaderUtilsTextureInput::enabledChanged, context: this, slot: &QQuick3DEffect::onTextureDirty);
646 connect(sender: &texture, signal: &QQuick3DShaderUtilsTextureInput::textureChanged, context: this, slot: &QQuick3DEffect::onTextureDirty);
647 } // else already connected
648 texProp.name = name;
649 if (texture.enabled && tex)
650 texProp.texImage = tex->getRenderImage();
651
652 texProp.shaderDataType = QSSGRenderShaderValue::Texture;
653
654 if (tex) {
655 texProp.minFilterType = tex->minFilter() == QQuick3DTexture::Nearest ? QSSGRenderTextureFilterOp::Nearest
656 : QSSGRenderTextureFilterOp::Linear;
657 texProp.magFilterType = tex->magFilter() == QQuick3DTexture::Nearest ? QSSGRenderTextureFilterOp::Nearest
658 : QSSGRenderTextureFilterOp::Linear;
659 texProp.mipFilterType = tex->generateMipmaps() ? (tex->mipFilter() == QQuick3DTexture::Nearest ? QSSGRenderTextureFilterOp::Nearest
660 : QSSGRenderTextureFilterOp::Linear)
661 : QSSGRenderTextureFilterOp::None;
662 texProp.horizontalClampType = tex->horizontalTiling() == QQuick3DTexture::Repeat ? QSSGRenderTextureCoordOp::Repeat
663 : (tex->horizontalTiling() == QQuick3DTexture::ClampToEdge ? QSSGRenderTextureCoordOp::ClampToEdge
664 : QSSGRenderTextureCoordOp::MirroredRepeat);
665 texProp.verticalClampType = tex->verticalTiling() == QQuick3DTexture::Repeat ? QSSGRenderTextureCoordOp::Repeat
666 : (tex->verticalTiling() == QQuick3DTexture::ClampToEdge ? QSSGRenderTextureCoordOp::ClampToEdge
667 : QSSGRenderTextureCoordOp::MirroredRepeat);
668 }
669
670 if (tex && QQuick3DObjectPrivate::get(item: tex)->type == QQuick3DObjectPrivate::Type::ImageCube)
671 uniforms.append(t: { QByteArrayLiteral("samplerCube"), name });
672 else if (tex && tex->textureData() && tex->textureData()->depth() > 0)
673 uniforms.append(t: { QByteArrayLiteral("sampler3D"), name });
674 else
675 uniforms.append(t: { QByteArrayLiteral("sampler2D"), name });
676
677 effectNode->textureProperties.push_back(t: texProp);
678 };
679
680 // Textures
681 for (const auto &property : std::as_const(t&: textureProperties))
682 processTextureProperty(*property.first, property.second);
683
684 if (effectNode->incompleteBuildTimeObject) { // This object came from the shadergen tool
685 const auto names = dynamicPropertyNames();
686 for (const auto &name : names) {
687 QVariant propValue = property(name: name.constData());
688 QMetaType propType = propValue.metaType();
689 if (propType == QMetaType(QMetaType::QVariant))
690 propType = propValue.metaType();
691
692 if (propType.id() >= QMetaType::User) {
693 if (propType.id() == qMetaTypeId<QQuick3DShaderUtilsTextureInput *>()) {
694 if (QQuick3DShaderUtilsTextureInput *texture = propValue.value<QQuick3DShaderUtilsTextureInput *>())
695 textureProperties.push_back(t: {texture, name});
696 }
697 } else if (propType.id() == QMetaType::QObjectStar) {
698 if (QQuick3DShaderUtilsTextureInput *texture = qobject_cast<QQuick3DShaderUtilsTextureInput *>(object: propValue.value<QObject *>()))
699 textureProperties.push_back(t: {texture, name});
700 } else {
701 const auto type = uniformType(type: propType);
702 if (type != QSSGRenderShaderValue::Unknown) {
703 uniforms.append(t: { uniformTypeName(type: propType), name });
704 effectNode->properties.push_back(t: { name, uniformTypeName(type: propType),
705 propValue, uniformType(type: propType), -1 /* aka. dynamic property */});
706 // We don't need to track property changes
707 } else {
708 // ### figure out how _not_ to warn when there are no dynamic
709 // properties defined (because warnings like Blah blah objectName etc. are not helpful)
710 qWarning(msg: "No known uniform conversion found for effect property %s. Skipping", name.constData());
711 }
712 }
713 }
714
715 for (const auto &property : std::as_const(t&: textureProperties))
716 processTextureProperty(*property.first, property.second);
717 }
718
719 // built-ins
720 uniforms.append(t: { "mat4", "qt_modelViewProjection" });
721 uniforms.append(t: { "sampler2D", "qt_inputTexture" });
722 uniforms.append(t: { "vec2", "qt_inputSize" });
723 uniforms.append(t: { "vec2", "qt_outputSize" });
724 uniforms.append(t: { "float", "qt_frame_num" });
725 uniforms.append(t: { "float", "qt_fps" });
726 uniforms.append(t: { "vec2", "qt_cameraProperties" });
727 uniforms.append(t: { "float", "qt_normalAdjustViewportFactor" });
728 uniforms.append(t: { "float", "qt_nearClipValue" });
729
730 QSSGShaderCustomMaterialAdapter::StringPairList builtinVertexInputs;
731 builtinVertexInputs.append(t: { "vec3", "attr_pos" });
732 builtinVertexInputs.append(t: { "vec2", "attr_uv" });
733
734 QSSGShaderCustomMaterialAdapter::StringPairList builtinVertexOutputs;
735 builtinVertexOutputs.append(t: { "vec2", "qt_inputUV" });
736 builtinVertexOutputs.append(t: { "vec2", "qt_textureUV" });
737
738 // fragOutput is added automatically by the program generator
739
740 if (!m_passes.isEmpty()) {
741 const QQmlContext *context = qmlContext(this);
742 effectNode->resetCommands();
743 for (QQuick3DShaderUtilsRenderPass *pass : std::as_const(t&: m_passes)) {
744 // Have a key composed more or less of the vertex and fragment filenames.
745 // The shaderLibraryManager uses stage+shaderPathKey as the key.
746 // Thus shaderPathKey is then sufficient to look up both the vertex and fragment shaders later on.
747 // Note that this key is not suitable as a unique key for the graphics resources because the same
748 // set of shader files can be used in multiple different passes, or in multiple active effects.
749 // But that's the effect system's problem.
750 QByteArray shaderPathKey("effect pipeline--");
751 QSSGRenderEffect::ShaderPrepPassData passData;
752 for (QQuick3DShaderUtilsShader::Stage stage : { QQuick3DShaderUtilsShader::Stage::Vertex, QQuick3DShaderUtilsShader::Stage::Fragment }) {
753 QQuick3DShaderUtilsShader *shader = nullptr;
754 for (QQuick3DShaderUtilsShader *s : pass->m_shaders) {
755 if (s->stage == stage) {
756 shader = s;
757 break;
758 }
759 }
760
761 // just how many enums does one need for the exact same thing...
762 QSSGShaderCache::ShaderType type = QSSGShaderCache::ShaderType::Vertex;
763 if (stage == QQuick3DShaderUtilsShader::Stage::Fragment)
764 type = QSSGShaderCache::ShaderType::Fragment;
765
766 // Will just use the custom material infrastructure. Some
767 // substitutions are common between custom materials and effects.
768 //
769 // Substitutions relevant to us here:
770 // MAIN -> qt_customMain
771 // FRAGCOLOR -> fragOutput
772 // POSITION -> gl_Position
773 // MODELVIEWPROJECTION_MATRIX -> qt_modelViewProjection
774 // DEPTH_TEXTURE -> qt_depthTexture
775 // ... other things shared with custom material
776 //
777 // INPUT -> qt_inputTexture
778 // INPUT_UV -> qt_inputUV
779 // ... other effect specifics
780 //
781 // Built-in uniforms, inputs and outputs will be baked into
782 // metadata comment blocks in the resulting source code.
783 // Same goes for inputs/outputs declared with VARYING.
784
785 QByteArray code;
786 if (shader) {
787 code = QSSGShaderUtils::resolveShader(fileUrl: shader->shader, context, shaderPathKey); // appends to shaderPathKey
788 } else {
789 if (!shaderPathKey.isEmpty())
790 shaderPathKey.append(c: '>');
791 shaderPathKey += "DEFAULT";
792 if (type == QSSGShaderCache::ShaderType::Vertex)
793 code = default_effect_vertex_shader;
794 else
795 code = default_effect_fragment_shader;
796 }
797
798 QByteArray shaderCodeMeta;
799 QSSGShaderCustomMaterialAdapter::ShaderCodeAndMetaData result;
800 if (type == QSSGShaderCache::ShaderType::Vertex) {
801 result = QSSGShaderCustomMaterialAdapter::prepareCustomShader(dst&: shaderCodeMeta, shaderCode: code, type,
802 baseUniforms: uniforms, baseInputs: builtinVertexInputs, baseOutputs: builtinVertexOutputs);
803 } else {
804 result = QSSGShaderCustomMaterialAdapter::prepareCustomShader(dst&: shaderCodeMeta, shaderCode: code, type,
805 baseUniforms: uniforms, baseInputs: builtinVertexOutputs);
806 }
807
808 if (result.second.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesDepthTexture))
809 effectNode->requiresDepthTexture = true;
810
811 code = result.first + shaderCodeMeta;
812
813 if (type == QSSGShaderCache::ShaderType::Vertex) {
814 // qt_customMain() has an argument list which gets injected here
815 insertVertexMainArgs(snippet&: code);
816 passData.vertexShaderCode = code;
817 passData.vertexMetaData = result.second;
818 } else {
819 passData.fragmentShaderCode = code;
820 passData.fragmentMetaData = result.second;
821 }
822 }
823
824 effectNode->commands.push_back(t: { .command: nullptr, .own: true }); // will be changed to QSSGBindShader in finalizeShaders
825 passData.bindShaderCmdIndex = effectNode->commands.size() - 1;
826
827 // finalizing the shader code happens in a separate step later on by the backend node
828 passData.shaderPathKeyPrefix = shaderPathKey;
829 effectNode->shaderPrepData.passes.append(t: passData);
830 effectNode->shaderPrepData.valid = true; // trigger reprocessing the shader code later on
831
832 effectNode->commands.push_back(t: { .command: new QSSGApplyInstanceValue, .own: true });
833
834 // Buffers
835 QQuick3DShaderUtilsBuffer *outputBuffer = pass->outputBuffer;
836 if (outputBuffer) {
837 const QByteArray &outBufferName = outputBuffer->name;
838 if (outBufferName.isEmpty()) {
839 // default output buffer (with settings)
840 auto outputFormat = QQuick3DShaderUtilsBuffer::mapTextureFormat(fmt: outputBuffer->format());
841 effectNode->commands.push_back(t: { .command: new QSSGBindTarget(outputFormat), .own: true });
842 effectNode->outputFormat = outputFormat;
843 } else {
844 // Allocate buffer command
845 effectNode->commands.push_back(t: { .command: outputBuffer->getCommand(), .own: false });
846 // bind buffer
847 effectNode->commands.push_back(t: { .command: new QSSGBindBuffer(outBufferName), .own: true });
848 }
849 } else {
850 // Use the default output buffer, same format as the source buffer
851 effectNode->commands.push_back(t: { .command: new QSSGBindTarget(QSSGRenderTextureFormat::Unknown), .own: true });
852 effectNode->outputFormat = QSSGRenderTextureFormat::Unknown;
853 }
854
855 // Other commands (BufferInput, Blending ... )
856 const auto &extraCommands = pass->m_commands;
857 for (const auto &command : extraCommands) {
858 const int bufferCount = command->bufferCount();
859 for (int i = 0; i != bufferCount; ++i)
860 effectNode->commands.push_back(t: { .command: command->bufferAt(idx: i)->getCommand(), .own: false });
861 effectNode->commands.push_back(t: { .command: command->getCommand(), .own: false });
862 }
863
864 effectNode->commands.push_back(t: { .command: new QSSGRender, .own: true });
865 }
866 }
867 }
868
869 if (m_dirtyAttributes & Dirty::PropertyDirty) {
870 for (const auto &prop : std::as_const(t&: effectNode->properties)) {
871 auto p = metaObject()->property(index: prop.pid);
872 if (Q_LIKELY(p.isValid()))
873 prop.value = p.read(obj: this);
874 }
875 }
876
877 m_dirtyAttributes = 0;
878
879 DebugViewHelpers::ensureDebugObjectName(node: effectNode, src: this);
880
881 return effectNode;
882}
883
884void QQuick3DEffect::onPropertyDirty()
885{
886 markDirty(type: Dirty::PropertyDirty);
887}
888
889void QQuick3DEffect::onTextureDirty()
890{
891 markDirty(type: Dirty::TextureDirty);
892}
893
894void QQuick3DEffect::onPassDirty()
895{
896 markDirty(type: Dirty::EffectChainDirty);
897}
898
899void QQuick3DEffect::effectChainDirty()
900{
901 markDirty(type: Dirty::EffectChainDirty);
902}
903
904void QQuick3DEffect::markDirty(QQuick3DEffect::Dirty type)
905{
906 if (!(m_dirtyAttributes & quint32(type))) {
907 m_dirtyAttributes |= quint32(type);
908 update();
909 }
910}
911
912void QQuick3DEffect::updateSceneManager(QQuick3DSceneManager *sceneManager)
913{
914 if (sceneManager) {
915 for (const auto &it : std::as_const(t&: m_dynamicTextureMaps)) {
916 if (auto tex = it->texture())
917 QQuick3DObjectPrivate::refSceneManager(obj: tex, mgr&: *sceneManager);
918 }
919 } else {
920 for (const auto &it : std::as_const(t&: m_dynamicTextureMaps)) {
921 if (auto tex = it->texture())
922 QQuick3DObjectPrivate::derefSceneManager(obj: tex);
923 }
924 }
925}
926
927void QQuick3DEffect::itemChange(QQuick3DObject::ItemChange change, const QQuick3DObject::ItemChangeData &value)
928{
929 if (change == QQuick3DObject::ItemSceneChange)
930 updateSceneManager(sceneManager: value.sceneManager);
931}
932
933void QQuick3DEffect::qmlAppendPass(QQmlListProperty<QQuick3DShaderUtilsRenderPass> *list, QQuick3DShaderUtilsRenderPass *pass)
934{
935 if (!pass)
936 return;
937
938 QQuick3DEffect *that = qobject_cast<QQuick3DEffect *>(object: list->object);
939 that->m_passes.push_back(t: pass);
940
941 connect(sender: pass, signal: &QQuick3DShaderUtilsRenderPass::changed, context: that, slot: &QQuick3DEffect::onPassDirty);
942 that->effectChainDirty();
943}
944
945QQuick3DShaderUtilsRenderPass *QQuick3DEffect::qmlPassAt(QQmlListProperty<QQuick3DShaderUtilsRenderPass> *list, qsizetype index)
946{
947 QQuick3DEffect *that = qobject_cast<QQuick3DEffect *>(object: list->object);
948 return that->m_passes.at(i: index);
949}
950
951qsizetype QQuick3DEffect::qmlPassCount(QQmlListProperty<QQuick3DShaderUtilsRenderPass> *list)
952{
953 QQuick3DEffect *that = qobject_cast<QQuick3DEffect *>(object: list->object);
954 return that->m_passes.size();
955}
956
957void QQuick3DEffect::qmlPassClear(QQmlListProperty<QQuick3DShaderUtilsRenderPass> *list)
958{
959 QQuick3DEffect *that = qobject_cast<QQuick3DEffect *>(object: list->object);
960
961 for (QQuick3DShaderUtilsRenderPass *pass : that->m_passes)
962 pass->disconnect(receiver: that);
963
964 that->m_passes.clear();
965 that->effectChainDirty();
966}
967
968void QQuick3DEffect::setDynamicTextureMap(QQuick3DShaderUtilsTextureInput *textureMap)
969{
970 // There can only be one texture input per property, as the texture input is a combination
971 // of the texture used and the uniform name!
972 auto it = m_dynamicTextureMaps.constFind(value: textureMap);
973
974 if (it == m_dynamicTextureMaps.constEnd()) {
975 // Track the object, if it's destroyed we need to remove it from our table.
976 connect(sender: textureMap, signal: &QQuick3DShaderUtilsTextureInput::destroyed, context: this, slot: [this, textureMap]() {
977 auto it = m_dynamicTextureMaps.constFind(value: textureMap);
978 if (it != m_dynamicTextureMaps.constEnd())
979 m_dynamicTextureMaps.erase(i: it);
980 });
981 m_dynamicTextureMaps.insert(value: textureMap);
982
983 update();
984 }
985}
986
987QT_END_NAMESPACE
988

source code of qtquick3d/src/quick3d/qquick3deffect.cpp