Revision control

++ // Copyright (C) 2020 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++
++ #include "qquick3dshaderutils_p.h"
++
++ #include <QtCore/qfile.h>
++ #include <QtQml/qqmlcontext.h>
++ #include <QtQml/qqmlfile.h>
++
++ #include "qquick3dviewport_p.h"
++ #include "qquick3dcustommaterial_p.h"
++ #include "qquick3deffect_p.h"
++ #include "qquick3dtextureproviderextension.h"
++ #include <ssg/qssgrenderextensions.h>
++ #include <ssg/qquick3dextensionhelpers.h>
++
++ #include <QtQuick3DRuntimeRender/private/qssgrenderimage_p.h>
++
++ #include <QtCore/QLoggingCategory>
++
++ QT_BEGIN_NAMESPACE
++
++ Q_LOGGING_CATEGORY(lcSubRenderPass, "qt.quick3d.subrenderpass")
++
++ /*!
++ \qmltype Shader
++ \inherits QtObject
++ \inqmlmodule QtQuick3D
++ \brief Container component for defining shader code used by post-processing effects.
++
++ The Shader type is used for populating the \l{Pass::shaders}{shaders} list in the
++ render \l{Pass}{pass} of an \l Effect.
++
++ A shader is code which is executed directly on the graphic hardware at a particular
++ \l{Shader::stage}{stage} of the rendering pipeline.
++
++ \sa Effect
++ */
++ /*!
++ \qmlproperty url Shader::shader
++ Specifies the name of the shader source file. For details on how to write shader code,
++ see the \l Effect documentation.
++
++ \warning Shader snippets are assumed to be trusted content. Application
++ developers are advised to carefully consider the potential implications
++ before allowing the loading of user-provided content that is not part of the
++ application.
++ */
++ /*!
++ \qmlproperty enumeration Shader::stage
++ Specifies the stage of the rendering pipeline when the shader code will be executed.
++ The default is \c Shader.Fragment
++
++ \value Shader.Vertex The shader is a vertex shader. This code is run once per vertex
++ in the input geometry and can be used to modify it before the geometry is rasterized
++ (scan converted). In the case of effects, the input geometry is always a quad (four
++ vertexes representing the corners of the render target).
++ \value Shader.Fragment The shader is a fragment shader. After vertex processing,
++ the modified geometry is turned into fragments (rasterization). Then a fragment shader
++ is executed for each fragment, assigning a color to it. Fragments are a related concept
++ to pixels, but with additional information attached. Also, as a result of some
++ anti-aliasing strategies, there may be more than one fragment for each pixel in the
++ output.
++ */
++
++ /*!
++ \qmltype TextureInput
++ \inherits QtObject
++ \inqmlmodule QtQuick3D
++ \brief Specifies a texture exposed to the shaders of a CustomMaterial or Effect.
++
++ This is a type which can be used for exposing a \l Texture to a shader, either
++ in the \l{Pass}{render pass} of an \l Effect, or in a \l CustomMaterial. It exists
++ primarily to assign a local name to the \l Texture that can be referenced from
++ shaders.
++
++ When a TextureInput property is declared in an \l Effect or a \l CustomMaterial,
++ it will automatically be available as a sampler in all shaders by its property
++ name.
++ */
++ /*!
++ \qmlproperty Texture TextureInput::texture
++ The texture for which this TextureInput serves as an indirection.
++ */
++ /*!
++ \qmlproperty bool TextureInput::enabled
++ The property determines if this TextureInput is enabled. The default value
++ is true. When disabled, the shaders of the effect sample a dummy, opaque
++ black texture instead of the one specified by \l texture.
++ */
++
++ /*!
++ \qmltype Pass
++ \inherits QtObject
++ \inqmlmodule QtQuick3D
++ \brief Defines a render pass in an Effect.
++
++ An \l Effect may consist of multiple render passes. Each render pass has a
++ setup phase where the list of \l{Pass::commands}{render commands} are executed,
++ a \l{Pass::output}{output buffer} and a list of \l{Pass::shaders}{shaders} to
++ use for rendering the effect.
++
++ See the documentation for \l Effect for more details on how to set up multiple
++ rendering passes.
++ */
++ /*!
++ \qmlproperty Buffer Pass::output
++ Specifies the output \l {Buffer}{buffer} of the pass.
++ */
++ /*!
++ \qmlproperty list Pass::commands
++ Specifies the list of render \l {Command}{commands} of the pass.
++ */
++ /*!
++ \qmlproperty list Pass::shaders
++ Specifies the list of \l {Shader}{shaders} of the pass.
++ */
++
++ /*!
++ \qmltype Command
++ \inherits QtObject
++ \inqmlmodule QtQuick3D
++ \brief Supertype of commands to be performed as part of a pass in an Effect.
++
++ The Command type should not be instantiated by itself, but only exists as a
++ polymorphic supertype for the different actions that can be performed as part
++ of a \l{Pass}{render pass}.
++
++ \sa BufferInput, SetUniformValue, Effect
++ */
++
++ /*!
++ \qmltype BufferInput
++ \inherits Command
++ \inqmlmodule QtQuick3D
++ \brief Defines an input buffer to be used as input for a pass of an Effect.
++
++ BufferInput is a \l Command which can be added to the list of commands in the \l Pass of
++ an \l Effect. When executed, it will expose the buffer as a sample to the shaders
++ in the render pass. The shaders must declare a sampler with the name given in the
++ BufferInput's \c sampler property.
++
++ This can be used for sharing intermediate results between the different passes of an
++ effect.
++
++ \sa TextureInput
++ */
++ /*!
++ \qmlproperty Buffer BufferInput::buffer
++ Specifies the \l {Buffer}{buffer} which should be exposed to the shader.
++ */
++ /*!
++ \qmlproperty string BufferInput::sampler
++ Specifies the name under which the buffer is exposed to the shader.
++ When this property is not set, the buffer is exposed with the built-in name \c INPUT.
++ */
++
++ /*!
++ \qmltype Buffer
++ \inherits QtObject
++ \inqmlmodule QtQuick3D
++ \brief Creates or references a color buffer to be used for a pass of an Effect.
++
++ A Buffer can be used to create intermediate buffers to share data between
++ \l{Pass}{render passes} in an \l Effect.
++
++ \note If the \l name property of the Buffer is empty, it will reference the
++ default output texture of the render pass.
++ */
++ /*!
++ \qmlproperty enumeration Buffer::format
++ Specifies the texture format. The default value is Buffer.RGBA8.
++
++ \value Buffer.RGBA8
++ \value Buffer.RGBA16F
++ \value Buffer.RGBA32F
++ \value Buffer.R8
++ \value Buffer.R16
++ \value Buffer.R16F
++ \value Buffer.R32F
++ */
++ /*!
++ \qmlproperty enumeration Buffer::textureFilterOperation
++ Specifies the texture filtering mode when sampling the contents of the
++ Buffer. The default value is Buffer.Linear.
++
++ \value Buffer.Nearest Use nearest-neighbor filtering.
++ \value Buffer.Linear Use linear filtering.
++ */
++ /*!
++ \qmlproperty enumeration Buffer::textureCoordOperation
++ Specifies the behavior for texture coordinates when sampling outside the [0, 1] range.
++ The default is Buffer.ClampToEdge.
++
++ \value Buffer.ClampToEdge Clamp coordinates to the edges.
++ \value Buffer.Repeat Wrap the coordinates at the edges to tile the texture.
++ \value Buffer.MirroredRepeat Wrap the coordinate at the edges, but mirror the texture
++ when tiling it.
++ */
++ /*!
++ \qmlproperty real Buffer::sizeMultiplier
++ Specifies the size multiplier of the buffer. For instance, a value of \c 1.0 creates
++ a buffer with the same size as the effect's input texture while \c 0.5 creates buffer
++ where both width and height is half as big. The default value is 1.0.
++ */
++ /*!
++ \qmlproperty enumeration Buffer::bufferFlags
++ Specifies the buffer allocation flags. The default is Buffer.None.
++
++ \value Buffer.None No special behavior.
++ \value Buffer.SceneLifetime The buffer is allocated for the whole lifetime of the scene.
++ */
++ /*!
++ \qmlproperty string Buffer::name
++ Specifies the name of the buffer.
++
++ \note When this property is empty, the Buffer will refer to the default output texture
++ of the \l{Pass}{render pass} instead of allocating a buffer. This can be useful to
++ override certain settings of the output, such as the texture format, without introducing
++ a new, separate intermediate texture.
++ */
++
++ /*!
++ \qmltype SetUniformValue
++ \inherits Command
++ \inqmlmodule QtQuick3D
++ \brief Defines a value to be set during a single \l {Pass}{pass}.
++ \since 5.15
++
++ SetUniformValue is a \l Command which can be added to the list of commands in a \l Pass. When
++ executed, it will set the uniform given by the \l{SetUniformValue::target}{target} property
++ to \l{SetUniformValue::value}{value}.
++
++ \note The value set by this command is will only be set during the \l {Pass}{pass} it occurs in.
++ For consecutive passes the value will be revert to the initial value of the uniform as it
++ was defined in the \l Effect item.
++
++ \sa BufferInput
++ */
++ /*!
++ \qmlproperty string SetUniformValue::target
++ Specifies the name of the uniform that will have its value changed during the \l {Pass}{pass}.
++ This must match the name of an existing property in the \l Effect.
++ */
++ /*!
++ \qmlproperty Variant SetUniformValue::value
++ Specifies the value that will be set on the \c target uniform.
++ */
++
++ /*!
++ \qmlvaluetype renderTargetBlend
++ \inqmlmodule QtQuick3D
++ \brief Defines blending parameters for a single color attachment of a render pass.
++ \since 6.11
++
++ The renderTargetBlend type is used to specify blending parameters for a single
++ color attachment of a \l RenderPass. An instance of renderTargetBlend can be
++ assigned to one of the \l PipelineStateOverride \c targetBlend0 through \c targetBlend7
++ properties, where the index corresponds to the color attachment slot.
++
++ The color blend equation is:
++ \badcode
++ result.rgb = srcColor * src.rgb opColor dstColor * dst.rgb
++ result.a = srcAlpha * src.a opAlpha dstAlpha * dst.a
++ \endcode
++ */
++
++ /*!
++ \qmlproperty bool renderTargetBlend::blendEnabled
++ If set to \c true, enables blending for the color attachment. If set to \c false,
++ disables blending and the output color is written directly to the attachment.
++ The default value is \c false.
++ */
++
++ /*!
++ \qmlproperty enumeration renderTargetBlend::colorWrite
++ A bitmask that controls which color channels are written to the color attachment.
++ Multiple values can be combined with the \c | operator.
++ The default value is \c {R | G | B | A}.
++
++ \value RenderTargetBlend.R Write to the red channel.
++ \value RenderTargetBlend.G Write to the green channel.
++ \value RenderTargetBlend.B Write to the blue channel.
++ \value RenderTargetBlend.A Write to the alpha channel.
++ */
++
++ /*!
++ \qmlproperty enumeration renderTargetBlend::srcColor
++ Sets the source color blend factor. This value scales the RGB components of the
++ fragment shader output before the blend operation. The default value is \c One.
++
++ \value RenderTargetBlend.Zero Factor is (0, 0, 0).
++ \value RenderTargetBlend.One Factor is (1, 1, 1).
++ \value RenderTargetBlend.SrcColor Factor is the source color (r, g, b).
++ \value RenderTargetBlend.OneMinusSrcColor Factor is (1-r, 1-g, 1-b) of the source.
++ \value RenderTargetBlend.DstColor Factor is the destination color (r, g, b).
++ \value RenderTargetBlend.OneMinusDstColor Factor is (1-r, 1-g, 1-b) of the destination.
++ \value RenderTargetBlend.SrcAlpha Factor is the source alpha (a, a, a).
++ \value RenderTargetBlend.OneMinusSrcAlpha Factor is (1-a, 1-a, 1-a) of the source.
++ \value RenderTargetBlend.DstAlpha Factor is the destination alpha (a, a, a).
++ \value RenderTargetBlend.OneMinusDstAlpha Factor is (1-a, 1-a, 1-a) of the destination.
++ \value RenderTargetBlend.ConstantColor Factor is the constant color set on the pipeline.
++ \value RenderTargetBlend.OneMinusConstantColor Factor is one minus the constant color.
++ \value RenderTargetBlend.ConstantAlpha Factor is the constant alpha.
++ \value RenderTargetBlend.OneMinusConstantAlpha Factor is one minus the constant alpha.
++ \value RenderTargetBlend.SrcAlphaSaturate Factor is min(src.a, 1-dst.a) for RGB, 1 for A.
++ \value RenderTargetBlend.Src1Color Factor is from the second color output of the fragment shader.
++ \value RenderTargetBlend.OneMinusSrc1Color Factor is one minus the second color output.
++ \value RenderTargetBlend.Src1Alpha Factor is the alpha of the second color output.
++ \value RenderTargetBlend.OneMinusSrc1Alpha Factor is one minus the alpha of the second output.
++ */
++
++ /*!
++ \qmlproperty enumeration renderTargetBlend::dstColor
++ Sets the destination color blend factor. This value scales the RGB components of the
++ current value in the color attachment before the blend operation.
++ The default value is \c OneMinusSrcAlpha.
++ See \c srcColor for the list of available values.
++ */
++
++ /*!
++ \qmlproperty enumeration renderTargetBlend::opColor
++ Sets the arithmetic operation used to combine the scaled source and destination
++ color components. The default value is \c Add.
++
++ \value RenderTargetBlend.Add Result = src + dst.
++ \value RenderTargetBlend.Subtract Result = src - dst.
++ \value RenderTargetBlend.ReverseSubtract Result = dst - src.
++ \value RenderTargetBlend.Min Result = min(src, dst).
++ \value RenderTargetBlend.Max Result = max(src, dst).
++ */
++
++ /*!
++ \qmlproperty enumeration renderTargetBlend::srcAlpha
++ Sets the source alpha blend factor. This value scales the alpha component of the
++ fragment shader output before the blend operation. The default value is \c One.
++ See \c srcColor for the list of available values.
++ */
++
++ /*!
++ \qmlproperty enumeration renderTargetBlend::dstAlpha
++ Sets the destination alpha blend factor. This value scales the alpha component of the
++ current value in the color attachment before the blend operation.
++ The default value is \c OneMinusSrcAlpha.
++ See \c srcColor for the list of available values.
++ */
++
++ /*!
++ \qmlproperty enumeration renderTargetBlend::opAlpha
++ Sets the arithmetic operation used to combine the scaled source and destination
++ alpha components. The default value is \c Add.
++ See \c opColor for the list of available values.
++ */
++
++ /*!
++ \qmltype PipelineStateOverride
++ \inherits Command
++ \inqmlmodule QtQuick3D
++ \brief Defines pipeline state overrides for a single \l {RenderPass}{pass}.
++ \since 6.11
++
++ PipelineStateOverride is a \l Command which can be added to the list of commands in a \l RenderPass.
++ When executed, it will override the pipeline state in the render pass according to the properties set on
++ the PipelineStateOverride. Only values that are set will override the existing pipeline state's values.
++ If you want to reset a value that has been overridden to the default, then set the property
++ to \c undefined.
++
++ \sa renderTargetBlend
++ */
++
++ /*!
++ \qmltype DepthStencilAttachment
++ \inherits Command
++ \inqmlmodule QtQuick3D
++ \brief Defines a depth-stencil attachment for a RenderPass.
++ \since 6.11
++
++ DepthStencilAttachment adds an implicit depth/stencil render buffer to a
++ \l RenderPass. The render buffer is automatically created and managed by the
++ rendering system. Because it is backed by an opaque render buffer rather
++ than a texture, its contents cannot be sampled in shaders. If you need to
++ read back depth values in a later pass, use \l DepthTextureAttachment instead.
++
++ Use DepthStencilAttachment when you need depth testing and/or stencil
++ operations in a pass but do not need to access the depth data afterwards.
++
++ \qml
++ RenderPass {
++ commands: [
++ ColorAttachment { name: "color0"; target: colorTexture },
++ // Add a depth buffer so depth testing works in this pass
++ DepthStencilAttachment {}
++ ]
++ }
++ \endqml
++
++ \sa DepthTextureAttachment, RenderPassTexture
++ */
++
++ namespace QSSGShaderUtils {
++
++ ResolveFunction resolveShaderOverride = nullptr;
++
++ void setResolveFunction(ResolveFunction fn)
++ {
++ resolveShaderOverride = fn;
++ }
++
++ QByteArray resolveShader(const QUrl &fileUrl, const QQmlContext *context, QByteArray &shaderPathKey)
++ {
++ if (resolveShaderOverride) {
++ QByteArray shaderData;
++ if (resolveShaderOverride(fileUrl, context, shaderData, shaderPathKey))
++ return shaderData;
++ }
++
++ if (!shaderPathKey.isEmpty())
++ shaderPathKey.append('>');
++
++ const QUrl loadUrl = context ? context->resolvedUrl(fileUrl) : fileUrl;
++ const QString filePath = QQmlFile::urlToLocalFileOrQrc(loadUrl);
++
++ QFile f(filePath);
++ if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
++ shaderPathKey += loadUrl.fileName().toUtf8();
++ return f.readAll();
++ } else {
++ qWarning("Failed to read shader code from %s", qPrintable(filePath));
++ }
++
++ return QByteArray();
++ }
++
++ // These are the QMetaTypes that we convert into uniforms.
++ static constexpr QMetaType::Type qssg_metatype_list[] {
++ QMetaType::Double,
++ QMetaType::Bool,
++ QMetaType::QVector2D,
++ QMetaType::QVector3D,
++ QMetaType::QVector4D,
++ QMetaType::Int,
++ QMetaType::QColor,
++ QMetaType::QSize,
++ QMetaType::QSizeF,
++ QMetaType::QPoint,
++ QMetaType::QPointF,
++ QMetaType::QRect,
++ QMetaType::QRectF,
++ QMetaType::QQuaternion,
++ QMetaType::QMatrix4x4
++ };
++
++ template<>
++ struct ShaderType<QMetaType::Double>
++ {
++ static constexpr QSSGRenderShaderValue::Type type() { return QSSGRenderShaderValue::Float; }
++ static QByteArray name() { return QByteArrayLiteral("float"); }
++ };
++
++ template<>
++ struct ShaderType<QMetaType::Bool>
++ {
++ static constexpr QSSGRenderShaderValue::Type type() { return QSSGRenderShaderValue::Boolean; }
++ static QByteArray name() { return QByteArrayLiteral("bool"); }
++ };
++
++ template<>
++ struct ShaderType<QMetaType::Int>
++ {
++ static constexpr QSSGRenderShaderValue::Type type() { return QSSGRenderShaderValue::Integer; }
++ static QByteArray name() { return QByteArrayLiteral("int"); }
++ };
++
++ template<>
++ struct ShaderType<QMetaType::QVector2D>
++ {
++ static constexpr QSSGRenderShaderValue::Type type() { return QSSGRenderShaderValue::Vec2; }
++ static QByteArray name() { return QByteArrayLiteral("vec2"); }
++ };
++
++ template<>
++ struct ShaderType<QMetaType::QVector3D>
++ {
++ static constexpr QSSGRenderShaderValue::Type type() { return QSSGRenderShaderValue::Vec3; }
++ static QByteArray name() { return QByteArrayLiteral("vec3"); }
++ };
++
++ template<>
++ struct ShaderType<QMetaType::QVector4D>
++ {
++ static constexpr QSSGRenderShaderValue::Type type() { return QSSGRenderShaderValue::Vec4; }
++ static QByteArray name() { return QByteArrayLiteral("vec4"); }
++ };
++
++ template<>
++ struct ShaderType<QMetaType::QColor>
++ {
++ static constexpr QSSGRenderShaderValue::Type type() { return QSSGRenderShaderValue::Rgba; }
++ static QByteArray name() { return QByteArrayLiteral("vec4"); }
++ };
++
++ template<>
++ struct ShaderType<QMetaType::QSize>
++ {
++ static constexpr QSSGRenderShaderValue::Type type() { return QSSGRenderShaderValue::Size; }
++ static QByteArray name() { return QByteArrayLiteral("vec2"); }
++ };
++
++ template<>
++ struct ShaderType<QMetaType::QSizeF>
++ {
++ static constexpr QSSGRenderShaderValue::Type type() { return QSSGRenderShaderValue::SizeF; }
++ static QByteArray name() { return QByteArrayLiteral("vec2"); }
++ };
++
++ template<>
++ struct ShaderType<QMetaType::QPoint>
++ {
++ static constexpr QSSGRenderShaderValue::Type type() { return QSSGRenderShaderValue::Point; }
++ static QByteArray name() { return QByteArrayLiteral("vec2"); }
++ };
++
++ template<>
++ struct ShaderType<QMetaType::QPointF>
++ {
++ static constexpr QSSGRenderShaderValue::Type type() { return QSSGRenderShaderValue::PointF; }
++ static QByteArray name() { return QByteArrayLiteral("vec2"); }
++ };
++
++ template<>
++ struct ShaderType<QMetaType::QRect>
++ {
++ static constexpr QSSGRenderShaderValue::Type type() { return QSSGRenderShaderValue::Rect; }
++ static QByteArray name() { return QByteArrayLiteral("vec4"); }
++ };
++
++ template<>
++ struct ShaderType<QMetaType::QRectF>
++ {
++ static constexpr QSSGRenderShaderValue::Type type() { return QSSGRenderShaderValue::RectF; }
++ static QByteArray name() { return QByteArrayLiteral("vec4"); }
++ };
++
++ template<>
++ struct ShaderType<QMetaType::QQuaternion>
++ {
++ static constexpr QSSGRenderShaderValue::Type type() { return QSSGRenderShaderValue::Quaternion; }
++ static QByteArray name() { return QByteArrayLiteral("vec4"); }
++ };
++
++ template<>
++ struct ShaderType<QMetaType::QMatrix4x4>
++ {
++ static constexpr QSSGRenderShaderValue::Type type() { return QSSGRenderShaderValue::Matrix4x4; }
++ static QByteArray name() { return QByteArrayLiteral("mat4"); }
++ };
++
++ QByteArray uniformTypeName(QMetaType type)
++ {
++ switch (type.id()) {
++ case QMetaType::Double:
++ case QMetaType::Float:
++ return ShaderType<QMetaType::Double>::name();
++ case QMetaType::Bool:
++ return ShaderType<QMetaType::Bool>::name();
++ case QMetaType::QVector2D:
++ return ShaderType<QMetaType::QVector2D>::name();
++ case QMetaType::QVector3D:
++ return ShaderType<QMetaType::QVector3D>::name();
++ case QMetaType::QVector4D:
++ return ShaderType<QMetaType::QVector4D>::name();
++ case QMetaType::Int:
++ return ShaderType<QMetaType::Int>::name();
++ case QMetaType::QColor:
++ return ShaderType<QMetaType::QColor>::name();
++ case QMetaType::QSize:
++ return ShaderType<QMetaType::QSize>::name();
++ case QMetaType::QSizeF:
++ return ShaderType<QMetaType::QSizeF>::name();
++ case QMetaType::QPoint:
++ return ShaderType<QMetaType::QPoint>::name();
++ case QMetaType::QPointF:
++ return ShaderType<QMetaType::QPointF>::name();
++ case QMetaType::QRect:
++ return ShaderType<QMetaType::QRect>::name();
++ case QMetaType::QRectF:
++ return ShaderType<QMetaType::QRectF>::name();
++ case QMetaType::QQuaternion:
++ return ShaderType<QMetaType::QQuaternion>::name();
++ case QMetaType::QMatrix4x4:
++ return ShaderType<QMetaType::QMatrix4x4>::name();
++ default:
++ return QByteArray();
++ }
++ }
++
++ QByteArray uniformTypeName(QSSGRenderShaderValue::Type type)
++ {
++ switch (type) {
++ case QSSGRenderShaderValue::Float:
++ return ShaderType<QMetaType::Double>::name();
++ case QSSGRenderShaderValue::Boolean:
++ return ShaderType<QMetaType::Bool>::name();
++ case QSSGRenderShaderValue::Integer:
++ return ShaderType<QMetaType::Int>::name();
++ case QSSGRenderShaderValue::Vec2:
++ return ShaderType<QMetaType::QVector2D>::name();
++ case QSSGRenderShaderValue::Vec3:
++ return ShaderType<QMetaType::QVector3D>::name();
++ case QSSGRenderShaderValue::Vec4:
++ return ShaderType<QMetaType::QVector4D>::name();
++ case QSSGRenderShaderValue::Rgba:
++ return ShaderType<QMetaType::QColor>::name();
++ case QSSGRenderShaderValue::Size:
++ return ShaderType<QMetaType::QSize>::name();
++ case QSSGRenderShaderValue::SizeF:
++ return ShaderType<QMetaType::QSizeF>::name();
++ case QSSGRenderShaderValue::Point:
++ return ShaderType<QMetaType::QPoint>::name();
++ case QSSGRenderShaderValue::PointF:
++ return ShaderType<QMetaType::QPointF>::name();
++ case QSSGRenderShaderValue::Rect:
++ return ShaderType<QMetaType::QRect>::name();
++ case QSSGRenderShaderValue::RectF:
++ return ShaderType<QMetaType::QRectF>::name();
++ case QSSGRenderShaderValue::Quaternion:
++ return ShaderType<QMetaType::QQuaternion>::name();
++ case QSSGRenderShaderValue::Matrix4x4:
++ return ShaderType<QMetaType::QMatrix4x4>::name();
++ default:
++ return QByteArray();
++ }
++ }
++
++ QSSGRenderShaderValue::Type uniformType(QMetaType type)
++ {
++ switch (type.id()) {
++ case QMetaType::Double:
++ case QMetaType::Float:
++ return ShaderType<QMetaType::Double>::type();
++ case QMetaType::Bool:
++ return ShaderType<QMetaType::Bool>::type();
++ case QMetaType::QVector2D:
++ return ShaderType<QMetaType::QVector2D>::type();
++ case QMetaType::QVector3D:
++ return ShaderType<QMetaType::QVector3D>::type();
++ case QMetaType::QVector4D:
++ return ShaderType<QMetaType::QVector4D>::type();
++ case QMetaType::Int:
++ return ShaderType<QMetaType::Int>::type();
++ case QMetaType::QColor:
++ return ShaderType<QMetaType::QColor>::type();
++ case QMetaType::QSize:
++ return ShaderType<QMetaType::QSize>::type();
++ case QMetaType::QSizeF:
++ return ShaderType<QMetaType::QSizeF>::type();
++ case QMetaType::QPoint:
++ return ShaderType<QMetaType::QPoint>::type();
++ case QMetaType::QPointF:
++ return ShaderType<QMetaType::QPointF>::type();
++ case QMetaType::QRect:
++ return ShaderType<QMetaType::QRect>::type();
++ case QMetaType::QRectF:
++ return ShaderType<QMetaType::QRectF>::type();
++ case QMetaType::QQuaternion:
++ return ShaderType<QMetaType::QQuaternion>::type();
++ case QMetaType::QMatrix4x4:
++ return ShaderType<QMetaType::QMatrix4x4>::type();
++ default:
++ return QSSGRenderShaderValue::Unknown;
++ }
++ }
++
++ MetaTypeList supportedMetatypes()
++ {
++ return {std::begin(qssg_metatype_list), std::end(qssg_metatype_list)};
++ }
++
++ }
++
++ QQuick3DShaderUtilsBuffer::TextureFormat QQuick3DShaderUtilsBuffer::mapRenderTextureFormat(QSSGRenderTextureFormat::Format fmt)
++ {
++ using TextureFormat = QQuick3DShaderUtilsBuffer::TextureFormat;
++ switch (fmt) {
++ case QSSGRenderTextureFormat::RGBA8: return TextureFormat::RGBA8;
++ case QSSGRenderTextureFormat::RGBA16F: return TextureFormat::RGBA16F;
++ case QSSGRenderTextureFormat::RGBA32F: return TextureFormat::RGBA32F;
++ case QSSGRenderTextureFormat::R8: return TextureFormat::R8;
++ case QSSGRenderTextureFormat::R16: return TextureFormat::R16;
++ case QSSGRenderTextureFormat::R16F: return TextureFormat::R16F;
++ case QSSGRenderTextureFormat::R32F: return TextureFormat::R32F;
++ default:
++ break;
++ }
++ return TextureFormat::Unknown;
++ }
++
++ QSSGRenderTextureFormat::Format QQuick3DShaderUtilsBuffer::mapTextureFormat(QQuick3DShaderUtilsBuffer::TextureFormat fmt)
++ {
++ using TextureFormat = QQuick3DShaderUtilsBuffer::TextureFormat;
++ switch (fmt) {
++ case TextureFormat::RGBA8: return QSSGRenderTextureFormat::RGBA8;
++ case TextureFormat::RGBA16F: return QSSGRenderTextureFormat::RGBA16F;
++ case TextureFormat::RGBA32F: return QSSGRenderTextureFormat::RGBA32F;
++ case TextureFormat::R8: return QSSGRenderTextureFormat::R8;
++ case TextureFormat::R16: return QSSGRenderTextureFormat::R16;
++ case TextureFormat::R16F: return QSSGRenderTextureFormat::R16F;
++ case TextureFormat::R32F: return QSSGRenderTextureFormat::R32F;
++ default:
++ break;
++ }
++ return QSSGRenderTextureFormat::Unknown;
++ }
++
++ QQuick3DShaderUtilsBuffer::TextureFormat QQuick3DShaderUtilsBuffer::format() const
++ {
++ return mapRenderTextureFormat(command.m_format.format);
++ }
++
++ void QQuick3DShaderUtilsBuffer::setFormat(TextureFormat format)
++ {
++ QSSGRenderTextureFormat::Format mappedTextureFormat = mapTextureFormat(format);
++ if (command.m_format == mappedTextureFormat)
++ return;
++
++ command.m_format = mappedTextureFormat;
++ emit changed();
++ }
++
++ void QQuick3DShaderUtilsBuffer::setTextureFilterOperation(TextureFilterOperation op)
++ {
++ if (command.m_filterOp == QSSGRenderTextureFilterOp(op))
++ return;
++
++ command.m_filterOp = QSSGRenderTextureFilterOp(op);
++ emit changed();
++ }
++
++ void QQuick3DShaderUtilsBuffer::setTextureCoordOperation(TextureCoordOperation texCoordOp)
++ {
++ if (command.m_texCoordOp == QSSGRenderTextureCoordOp(texCoordOp))
++ return;
++
++ command.m_texCoordOp = QSSGRenderTextureCoordOp(texCoordOp);
++ emit changed();
++ }
++
++ void QQuick3DShaderUtilsBuffer::setBufferFlags(AllocateBufferFlagValues flag)
++ {
++ if (quint32(command.m_bufferFlags) == quint32(flag))
++ return;
++
++ command.m_bufferFlags = quint32(flag);
++ emit changed();
++ }
++
++ QQuick3DShaderUtilsRenderPass::~QQuick3DShaderUtilsRenderPass()
++ {
++
++ }
++
++ void QQuick3DShaderUtilsRenderPass::qmlAppendCommand(QQmlListProperty<QQuick3DShaderUtilsRenderCommand> *list,
++ QQuick3DShaderUtilsRenderCommand *command)
++ {
++ if (!command)
++ return;
++
++ QQuick3DShaderUtilsRenderPass *that = qobject_cast<QQuick3DShaderUtilsRenderPass *>(list->object);
++ that->m_commands.push_back(command);
++ emit that->changed();
++ }
++
++ QQuick3DShaderUtilsRenderCommand *QQuick3DShaderUtilsRenderPass::qmlCommandAt(QQmlListProperty<QQuick3DShaderUtilsRenderCommand> *list,
++ qsizetype index)
++ {
++ QQuick3DShaderUtilsRenderPass *that = qobject_cast<QQuick3DShaderUtilsRenderPass *>(list->object);
++ return that->m_commands.at(index);
++ }
++
++ qsizetype QQuick3DShaderUtilsRenderPass::qmlCommandCount(QQmlListProperty<QQuick3DShaderUtilsRenderCommand> *list)
++ {
++ QQuick3DShaderUtilsRenderPass *that = qobject_cast<QQuick3DShaderUtilsRenderPass *>(list->object);
++ return that->m_commands.size();
++ }
++
++ void QQuick3DShaderUtilsRenderPass::qmlCommandClear(QQmlListProperty<QQuick3DShaderUtilsRenderCommand> *list)
++ {
++ QQuick3DShaderUtilsRenderPass *that = qobject_cast<QQuick3DShaderUtilsRenderPass *>(list->object);
++ that->m_commands.clear();
++ emit that->changed();
++ }
++
++ QQmlListProperty<QQuick3DShaderUtilsRenderCommand> QQuick3DShaderUtilsRenderPass::commands()
++ {
++ return QQmlListProperty<QQuick3DShaderUtilsRenderCommand>(this,
++ nullptr,
++ QQuick3DShaderUtilsRenderPass::qmlAppendCommand,
++ QQuick3DShaderUtilsRenderPass::qmlCommandCount,
++ QQuick3DShaderUtilsRenderPass::qmlCommandAt,
++ QQuick3DShaderUtilsRenderPass::qmlCommandClear);
++ }
++
++ void QQuick3DShaderUtilsRenderPass::qmlAppendShader(QQmlListProperty<QQuick3DShaderUtilsShader> *list,
++ QQuick3DShaderUtilsShader *shader)
++ {
++ if (!shader)
++ return;
++
++ QQuick3DShaderUtilsRenderPass *that = qobject_cast<QQuick3DShaderUtilsRenderPass *>(list->object);
++
++ // An append implementation CANNOT rely on the object (shader in this case)
++ // being complete. When the list references a Shader object living under
++ // another Effect, its properties may not be set at the point of this
++ // function being called, so accessing shader->stage is not allowed since
++ // it may still have its default value, not what is set from QML...
++
++ // the only thing we can do is to append to our list, do not try to be clever
++ that->m_shaders.append(shader);
++
++ connect(shader, &QQuick3DShaderUtilsShader::shaderChanged, that, &QQuick3DShaderUtilsRenderPass::changed);
++ connect(shader, &QQuick3DShaderUtilsShader::stageChanged, that, &QQuick3DShaderUtilsRenderPass::changed);
++
++ emit that->changed();
++ }
++
++ QQuick3DShaderUtilsShader *QQuick3DShaderUtilsRenderPass::qmlShaderAt(QQmlListProperty<QQuick3DShaderUtilsShader> *list,
++ qsizetype index)
++ {
++ QQuick3DShaderUtilsRenderPass *that = qobject_cast<QQuick3DShaderUtilsRenderPass *>(list->object);
++ return that->m_shaders.at(index);
++ }
++
++ qsizetype QQuick3DShaderUtilsRenderPass::qmlShaderCount(QQmlListProperty<QQuick3DShaderUtilsShader> *list)
++ {
++ QQuick3DShaderUtilsRenderPass *that = qobject_cast<QQuick3DShaderUtilsRenderPass *>(list->object);
++ return that->m_shaders.size();
++ }
++
++ void QQuick3DShaderUtilsRenderPass::qmlShaderClear(QQmlListProperty<QQuick3DShaderUtilsShader> *list)
++ {
++ QQuick3DShaderUtilsRenderPass *that = qobject_cast<QQuick3DShaderUtilsRenderPass *>(list->object);
++
++ for (QQuick3DShaderUtilsShader *shader : that->m_shaders)
++ shader->disconnect(that);
++
++ that->m_shaders.clear();
++
++ emit that->changed();
++ }
++
++ QQmlListProperty<QQuick3DShaderUtilsShader> QQuick3DShaderUtilsRenderPass::shaders()
++ {
++ return QQmlListProperty<QQuick3DShaderUtilsShader>(this,
++ nullptr,
++ QQuick3DShaderUtilsRenderPass::qmlAppendShader,
++ QQuick3DShaderUtilsRenderPass::qmlShaderCount,
++ QQuick3DShaderUtilsRenderPass::qmlShaderAt,
++ QQuick3DShaderUtilsRenderPass::qmlShaderClear);
++ }
++
++ QQuick3DShaderUtilsTextureInput::QQuick3DShaderUtilsTextureInput(QQuick3DObject *p) : QQuick3DObject(p) {}
++
++ QQuick3DShaderUtilsTextureInput::~QQuick3DShaderUtilsTextureInput()
++ {
++ }
++
++ void QQuick3DShaderUtilsTextureInput::setTexture(QQuick3DTexture *texture)
++ {
++ if (m_texture == texture)
++ return;
++
++ QObject *p = parent();
++ while (p != nullptr) {
++ if (QQuick3DCustomMaterial *mat = qobject_cast<QQuick3DCustomMaterial *>(p)) {
++ mat->setDynamicTextureMap(this);
++ QQuick3DObjectPrivate::attachWatcherPriv(mat, this, &QQuick3DShaderUtilsTextureInput::setTexture, texture, m_texture);
++ break;
++ } else if (QQuick3DEffect *efx = qobject_cast<QQuick3DEffect *>(p)) {
++ efx->setDynamicTextureMap(this);
++ QQuick3DObjectPrivate::attachWatcherPriv(efx, this, &QQuick3DShaderUtilsTextureInput::setTexture, texture, m_texture);
++ break;
++ }
++ p = p->parent();
++ }
++
++ if (p == nullptr) {
++ qWarning("A TextureInput was defined without a CustomMaterial or Effect ancestor. This should be avoided.");
++ }
++
++ m_texture = texture;
++ Q_EMIT textureChanged();
++ }
++
++ /*!
++ \qmltype RenderablesFilter
++ \inherits Command
++ \inqmlmodule QtQuick3D
++ \brief Defines a filter for selecting which renderables to affect in a \l {RenderPass}{pass}.
++ \since 6.11
++
++ The RenderablesFilter type is used to specify which renderables in the scene
++ should be affected by a \l RenderPass. By setting the \c renderableTypes property,
++ you can control whether the pass affects opaque objects, transparent objects, or
++ no objects at all.
++
++ Setting \c renderableTypes to \c None is useful when a RenderPass acts as a container
++ for \l SubRenderPass commands and should not render any scene objects itself.
++
++ In addition to filtering by renderable types, you can use the \l {RenderablesFilter::layerMask}{layerMask}
++ to further refine which renderables are affected based on their assigned \l{Node::layers}{layers}.
++
++ \qml
++ // A pass that renders only opaque scene objects
++ RenderPass {
++ id: opaquePass
++ commands: [
++ ColorAttachment { name: "color0"; target: opaqueColorTexture },
++ DepthTextureAttachment { target: depthTexture },
++ RenderablesFilter {
++ renderableTypes: RenderablesFilter.Opaque
++ }
++ ]
++ }
++
++ // A pass that renders only transparent objects on top
++ RenderPass {
++ id: transparentPass
++ commands: [
++ ColorAttachment { name: "color0"; target: transparentColorTexture },
++ RenderablesFilter {
++ renderableTypes: RenderablesFilter.Transparent
++ }
++ ]
++ }
++ \endqml
++ */
++
++ /*!
++ \qmlproperty int RenderablesFilter::layerMask
++ Sets the layer mask for the filter. Only renderables on the specified layers will be affected by the filter.
++
++ \sa Node::layers
++ */
++
++ /*!
++ \qmlproperty enumeration RenderablesFilter::renderableTypes
++ Sets the types of renderables that the filter will affect.
++
++ \value RenderablesFilter.None No renderables will be rendered. Useful for container passes that only have SubRenderPasses.
++ \value RenderablesFilter.Opaque Only opaque renderables will be rendered.
++ \value RenderablesFilter.Transparent Only transparent renderables will be rendered.
++
++ \note Multiple values can be combined using the | operator.
++
++ \default RenderablesFilter.Opaque | RenderablesFilter.Transparent
++ */
++ QQuick3DShaderUtilsRenderablesFilter::RenderableTypes QQuick3DShaderUtilsRenderablesFilter::renderableTypes() const
++ {
++ return static_cast<QQuick3DShaderUtilsRenderablesFilter::RenderableTypes>(command.renderableTypes);
++ }
++
++ QQuick3DShaderUtilsRenderablesFilter::~QQuick3DShaderUtilsRenderablesFilter()
++ {
++
++ }
++
++ void QQuick3DShaderUtilsRenderablesFilter::setRenderableTypes(RenderableTypes types)
++ {
++ command.renderableTypes = static_cast<QSSGRenderablesFilterCommand::RenderableTypeT>(types.toInt());
++ }
++
++ QQuick3DShaderUtilsPipelineStateOverride::QQuick3DShaderUtilsPipelineStateOverride()
++ {
++ // Forward all our own signals to the base class changed() signal so that
++ // QQuick3DRenderPass can connect once and be notified of any change.
++ const QMetaObject *mo = metaObject();
++ const QMetaMethod changedSignal = QMetaMethod::fromSignal(&QQuick3DShaderUtilsRenderCommand::changed);
++ for (int i = QQuick3DShaderUtilsRenderCommand::staticMetaObject.methodCount(); i < mo->methodCount(); ++i) {
++ const QMetaMethod mm = mo->method(i);
++ if (mm.methodType() == QMetaMethod::Signal)
++ connect(this, mm, this, changedSignal);
++ }
++ }
++
++ QQuick3DShaderUtilsPipelineStateOverride::~QQuick3DShaderUtilsPipelineStateOverride()
++ {
++
++ }
++
++ /*!
++ \qmlproperty bool PipelineStateOverride::depthTestEnabled
++ If set to \c true, enables depth testing for the render pass. If set to \c false, disables depth testing.
++ Setting this property to \c true requires a depth attachment for the render pass.
++ */
++ bool QQuick3DShaderUtilsPipelineStateOverride::depthTestEnabled() const
++ {
++ if (command.m_depthTestEnabled)
++ return *command.m_depthTestEnabled;
++ return false;
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::setDepthTestEnabled(bool newDepthTestEnabled)
++ {
++ if (command.m_depthTestEnabled && *command.m_depthTestEnabled == newDepthTestEnabled)
++ return;
++ command.m_depthTestEnabled = newDepthTestEnabled;
++ emit depthTestEnabledChanged();
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::resetDepthTestEnabled()
++ {
++ command.m_depthTestEnabled.reset();
++ emit depthTestEnabledChanged();
++ }
++
++ /*!
++ \qmlproperty bool PipelineStateOverride::depthWriteEnabled
++ If set to \c true, enables depth writing for the render pass. If set to \c false, disables depth writing.
++ Setting this property to \c true requires a depth attachment for the render pass.
++ */
++ bool QQuick3DShaderUtilsPipelineStateOverride::depthWriteEnabled() const
++ {
++ if (command.m_depthWriteEnabled)
++ return *command.m_depthWriteEnabled;
++ return false;
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::setDepthWriteEnabled(bool newDepthWriteEnabled)
++ {
++ if (command.m_depthWriteEnabled && *command.m_depthWriteEnabled == newDepthWriteEnabled)
++ return;
++ command.m_depthWriteEnabled = newDepthWriteEnabled;
++ emit depthWriteEnabledChanged();
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::resetDepthWriteEnabled()
++ {
++ command.m_depthWriteEnabled.reset();
++ emit depthWriteEnabledChanged();
++ }
++
++ /*!
++ \qmlproperty bool PipelineStateOverride::blendEnabled
++ If set to \c true, enables blending for the render pass. If set to \c false, disables blending.
++ For per-attachment blend settings, use the \c targetBlend0 through \c targetBlend7 properties.
++ */
++ bool QQuick3DShaderUtilsPipelineStateOverride::blendEnabled() const
++ {
++ if (command.m_blendEnabled)
++ return *command.m_blendEnabled;
++ return false;
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::setBlendEnabled(bool newBlendEnabled)
++ {
++ if (command.m_blendEnabled && *command.m_blendEnabled == newBlendEnabled)
++ return;
++ command.m_blendEnabled = newBlendEnabled;
++ emit blendEnabledChanged();
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::resetBlendEnabled()
++ {
++ command.m_blendEnabled.reset();
++ emit blendEnabledChanged();
++ }
++
++ /*!
++ \qmlproperty bool PipelineStateOverride::usesStencilReference
++ If set to \c true, enables the use of the stencil reference value for the render pass. If set to \c false,
++ disables the use of the stencil reference value.
++ */
++ bool QQuick3DShaderUtilsPipelineStateOverride::usesStencilReference() const
++ {
++ if (command.m_usesStencilReference)
++ return *command.m_usesStencilReference;
++ return false;
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::setUsesStencilReference(bool newUsesStencilReference)
++ {
++ if (command.m_usesStencilReference && *command.m_usesStencilReference == newUsesStencilReference)
++ return;
++ command.m_usesStencilReference = newUsesStencilReference;
++ emit usesStencilReferenceChanged();
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::resetUsesStencilReference()
++ {
++ command.m_usesStencilReference.reset();
++ emit usesStencilReferenceChanged();
++ }
++
++ /*!
++ \qmlproperty bool PipelineStateOverride::usesScissor
++ If set to \c true, enables scissor testing for the render pass. If set to \c false,
++ disables the scissor test. Use the \c scissor property to set the scissor rectangle.
++ */
++ bool QQuick3DShaderUtilsPipelineStateOverride::usesScissor() const
++ {
++ if (command.m_usesScissor)
++ return *command.m_usesScissor;
++ return false;
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::setUsesScissor(bool newUsesScissor)
++ {
++ if (command.m_usesScissor && *command.m_usesScissor == newUsesScissor)
++ return;
++ command.m_usesScissor = newUsesScissor;
++ emit usesScissorChanged();
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::resetUsesScissor()
++ {
++ command.m_usesScissor.reset();
++ emit usesScissorChanged();
++ }
++
++ /*!
++ \qmlproperty enumeration PipelineStateOverride::depthFunction
++ Sets the depth comparison function for the render pass.
++
++ \value PipelineStateOverride.Never The depth test never passes.
++ \value PipelineStateOverride.Less The depth test passes when the incoming depth is less than the stored depth.
++ \value PipelineStateOverride.Equal The depth test passes when the incoming depth equals the stored depth.
++ \value PipelineStateOverride.LessOrEqual The depth test passes when the incoming depth is less than or equal to the stored depth.
++ \value PipelineStateOverride.Greater The depth test passes when the incoming depth is greater than the stored depth.
++ \value PipelineStateOverride.NotEqual The depth test passes when the incoming depth does not equal the stored depth.
++ \value PipelineStateOverride.GreaterOrEqual The depth test passes when the incoming depth is greater than or equal to the stored depth.
++ \value PipelineStateOverride.Always The depth test always passes.
++ */
++ QQuick3DShaderUtilsPipelineStateOverride::CompareOperation QQuick3DShaderUtilsPipelineStateOverride::depthFunction() const
++ {
++ if (command.m_depthFunction)
++ return CompareOperation(*command.m_depthFunction);
++ return CompareOperation::Less;
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::setDepthFunction(CompareOperation newDepthFunction)
++ {
++ if (command.m_depthFunction && *command.m_depthFunction == QRhiGraphicsPipeline::CompareOp(newDepthFunction))
++ return;
++ command.m_depthFunction = QRhiGraphicsPipeline::CompareOp(newDepthFunction);
++ emit depthFunctionChanged();
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::resetDepthFunction()
++ {
++ command.m_depthFunction.reset();
++ emit depthFunctionChanged();
++ }
++
++ /*!
++ \qmlproperty enumeration PipelineStateOverride::cullMode
++ Sets the face culling mode for the render pass.
++
++ \value PipelineStateOverride.None No face culling.
++ \value PipelineStateOverride.Front Front-facing polygons are culled.
++ \value PipelineStateOverride.Back Back-facing polygons are culled.
++ */
++ QQuick3DShaderUtilsPipelineStateOverride::CullMode QQuick3DShaderUtilsPipelineStateOverride::cullMode() const
++ {
++ if (command.m_cullMode)
++ return CullMode(*command.m_cullMode);
++ return CullMode::Back;
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::setCullMode(CullMode newCullMode)
++ {
++ if (command.m_cullMode && *command.m_cullMode == QRhiGraphicsPipeline::CullMode(newCullMode))
++ return;
++ command.m_cullMode = QRhiGraphicsPipeline::CullMode(newCullMode);
++ emit cullModeChanged();
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::resetCullMode()
++ {
++ command.m_cullMode.reset();
++ emit cullModeChanged();
++ }
++
++ /*!
++ \qmlproperty enumeration PipelineStateOverride::polygonMode
++ Sets the polygon rasterization mode for the render pass.
++
++ \value PipelineStateOverride.Fill Polygons are filled (default).
++ \value PipelineStateOverride.Line Polygon edges are drawn as lines (wireframe).
++ */
++ QQuick3DShaderUtilsPipelineStateOverride::PolygonMode QQuick3DShaderUtilsPipelineStateOverride::polygonMode() const
++ {
++ if (command.m_polygonMode)
++ return PolygonMode(*command.m_polygonMode);
++ return PolygonMode::Fill;
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::setPolygonMode(PolygonMode newPolygonMode)
++ {
++ if (command.m_polygonMode && *command.m_polygonMode == QRhiGraphicsPipeline::PolygonMode(newPolygonMode))
++ return;
++ command.m_polygonMode = QRhiGraphicsPipeline::PolygonMode(newPolygonMode);
++ emit polygonModeChanged();
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::resetPolygonMode()
++ {
++ command.m_polygonMode.reset();
++ emit polygonModeChanged();
++ }
++
++ /*!
++ \qmlproperty uint PipelineStateOverride::stencilWriteMask
++ Sets the stencil write mask for the render pass. Each bit controls whether the corresponding
++ bit in the stencil buffer can be written.
++ */
++ quint32 QQuick3DShaderUtilsPipelineStateOverride::stencilWriteMask() const
++ {
++ if (command.m_stencilWriteMask)
++ return *command.m_stencilWriteMask;
++ return 0;
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::setStencilWriteMask(quint32 newStencilWriteMask)
++ {
++ if (command.m_stencilWriteMask && *command.m_stencilWriteMask == newStencilWriteMask)
++ return;
++ command.m_stencilWriteMask = newStencilWriteMask;
++ emit stencilWriteMaskChanged();
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::resetStencilWriteMask()
++ {
++ command.m_stencilWriteMask.reset();
++ emit stencilWriteMaskChanged();
++ }
++
++ /*!
++ \qmlproperty uint PipelineStateOverride::stencilReference
++ Sets the stencil reference value for the render pass. This value is used in stencil
++ comparison operations when \c usesStencilReference is \c true.
++ */
++ quint32 QQuick3DShaderUtilsPipelineStateOverride::stencilReference() const
++ {
++ if (command.m_stencilReference)
++ return *command.m_stencilReference;
++ return 0;
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::setStencilReference(quint32 newStencilReference)
++ {
++ if (command.m_stencilReference && *command.m_stencilReference == newStencilReference)
++ return;
++ command.m_stencilReference = newStencilReference;
++ emit stencilReferenceChanged();
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::resetStencilReference()
++ {
++ command.m_stencilReference.reset();
++ emit stencilReferenceChanged();
++ }
++
++ /*!
++ \qmlproperty rect PipelineStateOverride::viewport
++ Sets the viewport rectangle for the render pass. The rectangle specifies the region
++ of the render target to draw into, in pixels (x, y, width, height).
++ If not set, the full render target dimensions are used.
++ */
++ QRectF QQuick3DShaderUtilsPipelineStateOverride::viewport() const
++ {
++ if (command.m_viewport) {
++ const QRhiViewport &vp = *command.m_viewport;
++ return QRectF(vp.viewport()[0], vp.viewport()[1], vp.viewport()[2], vp.viewport()[3]);
++ }
++ return QRectF();
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::setViewport(const QRectF &newViewport)
++ {
++ if (command.m_viewport) {
++ const QRhiViewport &vp = *command.m_viewport;
++ if (vp.viewport()[0] == newViewport.x() &&
++ vp.viewport()[1] == newViewport.y() &&
++ vp.viewport()[2] == newViewport.width() &&
++ vp.viewport()[3] == newViewport.height())
++ return;
++ }
++ command.m_viewport = QRhiViewport(newViewport.x(), newViewport.y(), newViewport.width(), newViewport.height());
++ emit viewportChanged();
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::resetViewport()
++ {
++ command.m_viewport.reset();
++ emit viewportChanged();
++ }
++
++ /*!
++ \qmlproperty rect PipelineStateOverride::scissor
++ Sets the scissor rectangle for the render pass. Fragments outside this rectangle are
++ discarded. Requires \c usesScissor to be \c true.
++ */
++ QRect QQuick3DShaderUtilsPipelineStateOverride::scissor() const
++ {
++ if (command.m_scissor) {
++ const QRhiScissor &sc = *command.m_scissor;
++ return QRect(sc.scissor()[0], sc.scissor()[1], sc.scissor()[2], sc.scissor()[3]);
++ }
++ return QRect();
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::setScissor(const QRect &newScissor)
++ {
++ if (command.m_viewport) {
++ const QRhiScissor &sc = *command.m_scissor;
++ if (sc.scissor()[0] == newScissor.x() &&
++ sc.scissor()[1] == newScissor.y() &&
++ sc.scissor()[2] == newScissor.width() &&
++ sc.scissor()[3] == newScissor.height())
++ return;
++ }
++ command.m_scissor = QRhiScissor(newScissor.x(), newScissor.y(), newScissor.width(), newScissor.height());
++ emit scissorChanged();
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::resetScissor()
++ {
++ command.m_scissor.reset();
++ }
++
++ /*!
++ \qmlproperty renderTargetBlend PipelineStateOverride::targetBlend0
++ Sets the blending parameters for color attachment 0 of the render pass.
++ \sa renderTargetBlend
++ */
++ QQuick3DRenderPassTargetBlend QQuick3DShaderUtilsPipelineStateOverride::targetBlend0() const
++ {
++ if (command.m_targetBlend0)
++ return *command.m_targetBlend0;
++ return {};
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::setTargetBlend0(const QQuick3DRenderPassTargetBlend &newTargetBlend0)
++ {
++ if (command.m_targetBlend0 && QQuick3DRenderPassTargetBlend(*command.m_targetBlend0) == newTargetBlend0)
++ return;
++
++ command.m_targetBlend0 = newTargetBlend0.toRhiTargetBlend();;
++ emit targetBlend0Changed();
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::resetTargetBlend0()
++ {
++ command.m_targetBlend0.reset();
++ emit targetBlend0Changed();
++ }
++
++ /*!
++ \qmlproperty renderTargetBlend PipelineStateOverride::targetBlend1
++ Sets the blending parameters for color attachment 1 of the render pass.
++ \sa renderTargetBlend
++ */
++ QQuick3DRenderPassTargetBlend QQuick3DShaderUtilsPipelineStateOverride::targetBlend1() const
++ {
++ if (command.m_targetBlend1)
++ return *command.m_targetBlend1;
++ return {};
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::setTargetBlend1(const QQuick3DRenderPassTargetBlend &newTargetBlend1)
++ {
++ if (command.m_targetBlend1 && QQuick3DRenderPassTargetBlend(*command.m_targetBlend1) == newTargetBlend1)
++ return;
++ command.m_targetBlend1 = newTargetBlend1.toRhiTargetBlend();
++ emit targetBlend1Changed();
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::resetTargetBlend1()
++ {
++ command.m_targetBlend1.reset();
++ emit targetBlend1Changed();
++ }
++
++ /*!
++ \qmlproperty renderTargetBlend PipelineStateOverride::targetBlend2
++ Sets the blending parameters for color attachment 2 of the render pass.
++ \sa renderTargetBlend
++ */
++ QQuick3DRenderPassTargetBlend QQuick3DShaderUtilsPipelineStateOverride::targetBlend2() const
++ {
++ if (command.m_targetBlend2)
++ return *command.m_targetBlend2;
++ return {};
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::setTargetBlend2(const QQuick3DRenderPassTargetBlend &newTargetBlend2)
++ {
++ if (command.m_targetBlend2 && QQuick3DRenderPassTargetBlend(*command.m_targetBlend2) == newTargetBlend2)
++ return;
++ command.m_targetBlend2 = newTargetBlend2.toRhiTargetBlend();
++ emit targetBlend2Changed();
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::resetTargetBlend2()
++ {
++ command.m_targetBlend2.reset();
++ emit targetBlend2Changed();
++ }
++
++ /*!
++ \qmlproperty renderTargetBlend PipelineStateOverride::targetBlend3
++ Sets the blending parameters for color attachment 3 of the render pass.
++ \sa renderTargetBlend
++ */
++ QQuick3DRenderPassTargetBlend QQuick3DShaderUtilsPipelineStateOverride::targetBlend3() const
++ {
++ if (command.m_targetBlend3)
++ return *command.m_targetBlend3;
++ return {};
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::setTargetBlend3(const QQuick3DRenderPassTargetBlend &newTargetBlend3)
++ {
++ if (command.m_targetBlend3 && QQuick3DRenderPassTargetBlend(*command.m_targetBlend3) == newTargetBlend3)
++ return;
++ command.m_targetBlend3 = newTargetBlend3.toRhiTargetBlend();
++ emit targetBlend3Changed();
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::resetTargetBlend3()
++ {
++ command.m_targetBlend3.reset();
++ emit targetBlend3Changed();
++ }
++
++ /*!
++ \qmlproperty renderTargetBlend PipelineStateOverride::targetBlend4
++ Sets the blending parameters for color attachment 4 of the render pass.
++ \sa renderTargetBlend
++ */
++ QQuick3DRenderPassTargetBlend QQuick3DShaderUtilsPipelineStateOverride::targetBlend4() const
++ {
++ if (command.m_targetBlend4)
++ return *command.m_targetBlend4;
++ return {};
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::setTargetBlend4(const QQuick3DRenderPassTargetBlend &newTargetBlend4)
++ {
++ if (command.m_targetBlend4 && QQuick3DRenderPassTargetBlend(*command.m_targetBlend4) == newTargetBlend4)
++ return;
++ command.m_targetBlend4 = newTargetBlend4.toRhiTargetBlend();
++ emit targetBlend4Changed();
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::resetTargetBlend4()
++ {
++ command.m_targetBlend4.reset();
++ emit targetBlend4Changed();
++ }
++
++ /*!
++ \qmlproperty renderTargetBlend PipelineStateOverride::targetBlend5
++ Sets the blending parameters for color attachment 5 of the render pass.
++ \sa renderTargetBlend
++ */
++ QQuick3DRenderPassTargetBlend QQuick3DShaderUtilsPipelineStateOverride::targetBlend5() const
++ {
++ if (command.m_targetBlend5)
++ return *command.m_targetBlend5;
++ return {};
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::setTargetBlend5(const QQuick3DRenderPassTargetBlend &newTargetBlend5)
++ {
++ if (command.m_targetBlend5 && QQuick3DRenderPassTargetBlend(*command.m_targetBlend5) == newTargetBlend5)
++ return;
++ command.m_targetBlend5 = newTargetBlend5.toRhiTargetBlend();
++ emit targetBlend5Changed();
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::resetTargetBlend5()
++ {
++ command.m_targetBlend5.reset();
++ emit targetBlend5Changed();
++ }
++
++ /*!
++ \qmlproperty renderTargetBlend PipelineStateOverride::targetBlend6
++ Sets the blending parameters for color attachment 6 of the render pass.
++ \sa renderTargetBlend
++ */
++ QQuick3DRenderPassTargetBlend QQuick3DShaderUtilsPipelineStateOverride::targetBlend6() const
++ {
++ if (command.m_targetBlend6)
++ return *command.m_targetBlend6;
++ return {};
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::setTargetBlend6(const QQuick3DRenderPassTargetBlend &newTargetBlend6)
++ {
++ if (command.m_targetBlend6 && QQuick3DRenderPassTargetBlend(*command.m_targetBlend6) == newTargetBlend6)
++ return;
++ command.m_targetBlend6 = newTargetBlend6.toRhiTargetBlend();
++ emit targetBlend6Changed();
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::resetTargetBlend6()
++ {
++ command.m_targetBlend6.reset();
++ emit targetBlend6Changed();
++ }
++
++ /*!
++ \qmlproperty renderTargetBlend PipelineStateOverride::targetBlend7
++ Sets the blending parameters for color attachment 7 of the render pass.
++ \sa renderTargetBlend
++ */
++ QQuick3DRenderPassTargetBlend QQuick3DShaderUtilsPipelineStateOverride::targetBlend7() const
++ {
++ if (command.m_targetBlend7)
++ return *command.m_targetBlend7;
++ return {};
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::setTargetBlend7(const QQuick3DRenderPassTargetBlend &newTargetBlend7)
++ {
++ if (command.m_targetBlend7 && QQuick3DRenderPassTargetBlend(*command.m_targetBlend7) == newTargetBlend7)
++ return;
++ command.m_targetBlend7 = newTargetBlend7.toRhiTargetBlend();
++ emit targetBlend7Changed();
++ }
++
++ void QQuick3DShaderUtilsPipelineStateOverride::resetTargetBlend7()
++ {
++ command.m_targetBlend7.reset();
++ emit targetBlend7Changed();
++ }
++
++ /*!
++ \qmltype RenderPassTexture
++ \inherits Object3D
++ \inqmlmodule QtQuick3D
++ \brief Defines a texture to be used as a render target in a \l {RenderPass}{pass}.
++ \since 6.11
++
++ RenderPassTexture declares an off-screen texture that a \l RenderPass can
++ render into. The texture is automatically created and managed by the
++ rendering system. Its size tracks the \l View3D viewport by default.
++
++ Once declared, attach the texture to a pass via \l ColorAttachment (for
++ color output) or \l DepthTextureAttachment (for depth output). The
++ resulting texture can then be accessed from a subsequent pass or a
++ \l Texture component using \l RenderOutputProvider.
++
++ \qml
++ // Declare a float color buffer
++ RenderPassTexture {
++ id: hdrColorTexture
++ format: RenderPassTexture.RGBA16F
++ }
++
++ RenderPass {
++ commands: [
++ ColorAttachment {
++ name: "color0"
++ target: hdrColorTexture
++ }
++ ]
++ }
++ \endqml
++
++ \sa ColorAttachment, DepthTextureAttachment, RenderOutputProvider
++ */
++
++ /*!
++ \qmlproperty enumeration RenderPassTexture::format
++ Sets the format of the render target texture.
++
++ Available formats are:
++
++ \value RenderPassTexture.Unknown
++ \value RenderPassTexture.RGBA8
++ \value RenderPassTexture.RGBA16F
++ \value RenderPassTexture.RGBA32F
++ \value RenderPassTexture.R8
++ \value RenderPassTexture.R16
++ \value RenderPassTexture.R16F
++ \value RenderPassTexture.R32F
++ \value RenderPassTexture.Depth16
++ \value RenderPassTexture.Depth24
++ \value RenderPassTexture.Depth32
++ \value RenderPassTexture.Depth24Stencil8
++ */
++
++ QQuick3DShaderUtilsRenderPassTexture::TextureFormat QQuick3DShaderUtilsRenderPassTexture::format() const
++ {
++ return fromRenderTextureFormat(command->format());
++ }
++
++ QQuick3DShaderUtilsRenderPassTexture::~QQuick3DShaderUtilsRenderPassTexture()
++ {
++
++ }
++
++ void QQuick3DShaderUtilsRenderPassTexture::setFormat(TextureFormat newFormat)
++ {
++ if (!command)
++ command = std::make_shared<QSSGAllocateTexture>();
++ command->setFormat(asRenderTextureFormat(newFormat));
++ }
++
++ QSSGRenderTextureFormat QQuick3DShaderUtilsRenderPassTexture::asRenderTextureFormat(TextureFormat fmt)
++ {
++ switch (fmt) {
++ case TextureFormat::Unknown: return QSSGRenderTextureFormat::Unknown;
++ case TextureFormat::RGBA8: return QSSGRenderTextureFormat::RGBA8;
++ case TextureFormat::RGBA16F: return QSSGRenderTextureFormat::RGBA16F;
++ case TextureFormat::RGBA32F: return QSSGRenderTextureFormat::RGBA32F;
++ case TextureFormat::R8: return QSSGRenderTextureFormat::R8;
++ case TextureFormat::R16: return QSSGRenderTextureFormat::R16;
++ case TextureFormat::R16F: return QSSGRenderTextureFormat::R16F;
++ case TextureFormat::R32F: return QSSGRenderTextureFormat::R32F;
++ case TextureFormat::Depth16: return QSSGRenderTextureFormat::Depth16;
++ case TextureFormat::Depth24: return QSSGRenderTextureFormat::Depth24;
++ case TextureFormat::Depth32: return QSSGRenderTextureFormat::Depth32;
++ case TextureFormat::Depth24Stencil8: return QSSGRenderTextureFormat::Depth24Stencil8;
++ default:
++ break;
++ }
++ return QSSGRenderTextureFormat::Unknown;
++ }
++
++ QQuick3DShaderUtilsRenderPassTexture::TextureFormat QQuick3DShaderUtilsRenderPassTexture::fromRenderTextureFormat(QSSGRenderTextureFormat fmt)
++ {
++ switch (fmt.format) {
++ case QSSGRenderTextureFormat::Unknown: return TextureFormat::Unknown;
++ case QSSGRenderTextureFormat::RGBA8: return TextureFormat::RGBA8;
++ case QSSGRenderTextureFormat::RGBA16F: return TextureFormat::RGBA16F;
++ case QSSGRenderTextureFormat::RGBA32F: return TextureFormat::RGBA32F;
++ case QSSGRenderTextureFormat::R8: return TextureFormat::R8;
++ case QSSGRenderTextureFormat::R16: return TextureFormat::R16;
++ case QSSGRenderTextureFormat::R16F: return TextureFormat::R16F;
++ case QSSGRenderTextureFormat::R32F: return TextureFormat::R32F;
++ case QSSGRenderTextureFormat::Depth16: return TextureFormat::Depth16;
++ case QSSGRenderTextureFormat::Depth24: return TextureFormat::Depth24;
++ case QSSGRenderTextureFormat::Depth32: return TextureFormat::Depth32;
++ case QSSGRenderTextureFormat::Depth24Stencil8: return TextureFormat::Depth24Stencil8;
++ default:
++ break;
++ }
++ return TextureFormat::Unknown;
++ }
++
++ /*!
++ \qmltype ColorAttachment
++ \inherits Command
++ \inqmlmodule QtQuick3D
++ \brief Defines a color attachment for a render pass.
++ \since 6.11
++
++ The ColorAttachment type is used to specify a color attachment for a \l RenderPass.
++ The \l name property is used to identify the attachment within the render pass.
++ If the \l {RenderPass::materialMode}{RenderPass.AugmentMaterial} mode is used,
++ the name will be exposed as an output with \c name in the fragment shader.
++
++ \sa RenderPassTexture
++
++ \qml
++ RenderPass {
++ // Define a render target texture
++ RenderPassTexture {
++ id: colorTexture
++ format: RenderPassTexture.RGBA16F
++ }
++
++ commands: [
++ // Define a color attachment using the texture
++ ColorAttachment {
++ name: "color0"
++ target: colorTexture
++ }
++ ]
++ }
++ \endqml
++ */
++
++ /*!
++ \qmlproperty RenderPassTexture ColorAttachment::target
++ The \l RenderPassTexture that will be used as the color attachment. The texture
++ must have a color-compatible format (e.g. \c RGBA8, \c RGBA16F).
++ */
++
++ /*!
++ \qmlproperty string ColorAttachment::name
++ The name used to identify this color attachment within the render pass. When
++ \l {RenderPass::materialMode}{materialMode} is set to \c AugmentMaterial, this name
++ is exposed as a fragment shader output variable, allowing custom shader code to
++ write to this attachment.
++ */
++
++ QQuick3DShaderUtilsRenderPassColorAttachment::~QQuick3DShaderUtilsRenderPassColorAttachment()
++ {
++
++ }
++
++ QByteArray QQuick3DShaderUtilsRenderPassColorAttachment::name() const
++ {
++ return m_name;
++ }
++
++ void QQuick3DShaderUtilsRenderPassColorAttachment::setName(const QByteArray &newName)
++ {
++ m_name = newName;
++ }
++
++ QSSGCommand *QQuick3DShaderUtilsRenderPassColorAttachment::cloneCommand()
++ {
++ if (target) {
++ QSSGColorAttachment *cmd = new QSSGColorAttachment(m_name);
++ cmd->m_textureCmd = target->command;
++ return cmd;
++ }
++
++ return nullptr;
++ }
++
++ QQuick3DPropertyChangedTracker::~QQuick3DPropertyChangedTracker()
++ {
++
++ }
++
++ std::optional<QQuick3DPropertyChangedTracker::UniformProperty> QQuick3DPropertyChangedTracker::createOrUpdateTrackedProperty(const QMetaProperty property,
++ bool addWatchers)
++ {
++ Q_ASSERT(property.isValid());
++
++ std::optional<QQuick3DPropertyChangedTracker::UniformProperty> result;
++ const int propertyIndex = property.propertyIndex();
++
++ static auto getSamplerHint = [](const QQuick3DTexture &texture) {
++ if (auto *po = QQuick3DObjectPrivate::get(&texture)) {
++ if (po->type == QQuick3DObjectPrivate::Type::TextureProvider) {
++ auto textureProvider = static_cast<QQuick3DTextureProviderExtension *>(texture.textureProvider());
++ switch (textureProvider->samplerHint()) {
++ case QQuick3DTextureProviderExtension::SamplerHint::Sampler2D:
++ return QSSGRenderSamplerType::Sampler2D;
++ case QQuick3DTextureProviderExtension::SamplerHint::Sampler2DArray:
++ return QSSGRenderSamplerType::Sampler2DArray;
++ case QQuick3DTextureProviderExtension::SamplerHint::Sampler3D:
++ return QSSGRenderSamplerType::Sampler3D;
++ case QQuick3DTextureProviderExtension::SamplerHint::SamplerCube:
++ return QSSGRenderSamplerType::SamplerCube;
++ case QQuick3DTextureProviderExtension::SamplerHint::SamplerCubeArray:
++ return QSSGRenderSamplerType::SamplerCubeArray;
++ case QQuick3DTextureProviderExtension::SamplerHint::SamplerBuffer:
++ return QSSGRenderSamplerType::SamplerBuffer;
++ }
++ } else if (po->type == QQuick3DObjectPrivate::Type::ImageCube) {
++ return QSSGRenderSamplerType::SamplerCube;
++ } else if (texture.textureData() && texture.textureData()->depth() > 0) {
++ return QSSGRenderSamplerType::Sampler3D;
++ }
++ }
++
++ return QSSGRenderSamplerType::Sampler2D;
++ };
++
++ const auto addTextureToUniforms =
++ [&](const char *name, QQuick3DTexture *texture, int propertyIndex) -> QQuick3DPropertyChangedTracker::UniformProperty {
++ QSSGRenderImage *ri = static_cast<QSSGRenderImage *>(QQuick3DObjectPrivate::get(texture)->spatialNode);
++ auto samplerName = QSSGBaseTypeHelpers::toString(getSamplerHint(*texture));
++ if (addWatchers) {
++ if (auto textureProvider = qobject_cast<QQuick3DTextureProviderExtension *>(texture->textureProvider())) {
++ // FIXME? We don't have a way of adding watchers after the initial extraction so switching
++ // textureProvider during runtime will not give a callback.
++ QObject::connect(textureProvider, &QQuick3DTextureProviderExtension::surfaceChanged, m_owner, [this, property, texture]() {
++ addPropertyWatcher(property, DirtyPropertyHint::Reference, texture);
++ });
++ }
++ QObject::connect(texture, &QQuick3DTexture::textureProviderChanged, m_owner, [this, property, texture]() {
++ addPropertyWatcher(property, DirtyPropertyHint::Reference, texture);
++ });
++ // Re-register watcher when the texture backend node is (re)created
++ QObject::connect(texture, &QQuick3DTexture::textureInternalChange, m_owner,
++ [this, property, texture](QQuick3DTextureInternalChange change) {
++ if (change.type == QQuick3DTextureInternalChange::Type::BackendNode)
++ addPropertyWatcher(property, DirtyPropertyHint::Reference, texture);
++ });
++ }
++
++ return UniformProperty(name, samplerName, QVariant::fromValue(ri), QSSGRenderShaderValue::Texture, propertyIndex);
++ };
++
++ // The TextureInput type needs extra watchers for its properties...
++ const auto addTextureInputWatchers = [&](QMetaProperty property, QQuick3DShaderUtilsTextureInput *textureInput) {
++ QObject::connect(textureInput, &QQuick3DShaderUtilsTextureInput::enabledChanged, m_owner, [this, property, textureInput]() {
++ addPropertyWatcher(property, DirtyPropertyHint::Reference, textureInput);
++ });
++ QObject::connect(textureInput, &QQuick3DShaderUtilsTextureInput::textureChanged, m_owner, [this, property, textureInput]() {
++ addPropertyWatcher(property, DirtyPropertyHint::Reference, textureInput);
++ });
++ };
++
++ const char *name = property.name();
++ QMetaType propType = property.metaType();
++ QVariant propValue = property.read(m_owner);
++ if (propType == QMetaType(QMetaType::QVariant))
++ propType = propValue.metaType();
++
++ const auto type = QSSGShaderUtils::uniformType(propType);
++ if (type != QSSGRenderShaderValue::Unknown) {
++ result = UniformProperty(name, QSSGShaderUtils::uniformTypeName(propType), propValue, QSSGShaderUtils::uniformType(propType), propertyIndex);
++ if (addWatchers)
++ addPropertyWatcher(property, DirtyPropertyHint::Value);
++ } else {
++ if (propType.id() >= QMetaType::User) {
++ if (propType.id() == qMetaTypeId<QQuick3DTexture *>()) {
++ if (QQuick3DTexture *texture = property.read(m_owner).value<QQuick3DTexture *>()) {
++ result = addTextureToUniforms(name, texture, propertyIndex);
++ if (addWatchers)
++ addPropertyWatcher(property, DirtyPropertyHint::Reference, texture);
++ }
++ } else if (propType.id() == qMetaTypeId<QQuick3DShaderUtilsTextureInput *>()) { // For compatibility, also check for texture input types
++ if (QQuick3DShaderUtilsTextureInput *textureInput = property.read(m_owner).value<QQuick3DShaderUtilsTextureInput *>();
++ textureInput && textureInput->texture()) {
++ result = addTextureToUniforms(property.name(), textureInput->texture(), propertyIndex);
++ if (addWatchers) {
++ addTextureInputWatchers(property, textureInput);
++ addPropertyWatcher(property, DirtyPropertyHint::Reference, textureInput);
++ }
++ }
++ }
++ } else if (propType == QMetaType(QMetaType::QObjectStar)) {
++ if (QQuick3DTexture *texture = qobject_cast<QQuick3DTexture *>(propValue.value<QObject *>())) {
++ result = addTextureToUniforms(name, texture, propertyIndex);
++ if (addWatchers)
++ addPropertyWatcher(property, DirtyPropertyHint::Reference, texture);
++ } else if (QQuick3DShaderUtilsTextureInput *textureInput = qobject_cast<QQuick3DShaderUtilsTextureInput *>(
++ propValue.value<QObject *>());
++ textureInput && textureInput->texture()) {
++ result = addTextureToUniforms(property.name(), textureInput->texture(), propertyIndex);
++ if (addWatchers) {
++ addTextureInputWatchers(property, textureInput);
++ addPropertyWatcher(property, DirtyPropertyHint::Reference, textureInput);
++ }
++ }
++ }
++ }
++
++ return result;
++ }
++
++ QList<QQuick3DPropertyChangedTracker::UniformProperty> QQuick3DPropertyChangedTracker::extractProperties()
++ {
++ if (!m_dirtyProperties.empty()) {
++ for (int propertyIndex : std::as_const(m_dirtyProperties)) {
++ auto it = std::lower_bound(m_propertyList.begin(),
++ m_propertyList.end(),
++ propertyIndex,
++ [](const UniformProperty &p, int index) { return p.pid < index; });
++ if (it == m_propertyList.end()) {
++ Q_ASSERT(false);
++ continue;
++ }
++
++ QMetaProperty property = m_owner->metaObject()->property(propertyIndex);
++ if (Q_UNLIKELY(!property.isValid())) {
++ m_propertyList.erase(it);
++ continue;
++ }
++
++ if (auto trackedProperty = createOrUpdateTrackedProperty(property, false); trackedProperty.has_value())
++ *it = *trackedProperty;
++ else
++ m_propertyList.erase(it);
++ }
++ m_dirtyProperties.clear();
++ }
++
++ if (m_extracted)
++ return m_propertyList;
++
++ auto metaObject = m_owner->metaObject();
++
++ // Properties -> uniforms
++ const int propCount = metaObject->propertyCount();
++ int propOffset = metaObject->propertyOffset();
++ m_propertyList.reserve(propCount);
++
++ // Classes can have multilayered inheritance structure, so find the actual propOffset by
++ // walking up the inheritance chain.
++ const QMetaObject *superClass = metaObject->superClass();
++ while (superClass && qstrcmp(superClass->className(), m_superClassName) != 0) {
++ propOffset = superClass->propertyOffset();
++ superClass = superClass->superClass();
++ }
++
++ for (int i = propOffset; i != propCount; ++i) {
++ const QMetaProperty property = metaObject->property(i);
++ if (Q_UNLIKELY(!property.isValid()))
++ continue;
++ if (auto uniformProperty = createOrUpdateTrackedProperty(property, true); uniformProperty.has_value()) {
++ m_propertyList.push_back(*uniformProperty);
++ }
++ }
++
++ m_dirtyProperties.reserve(m_propertyList.size());
++
++ m_extracted = true;
++ return m_propertyList;
++ }
++
++ void QQuick3DPropertyChangedTracker::addPropertyWatcher(QMetaProperty property, DirtyPropertyHint hint, QQuick3DObject *object)
++ {
++ if (property.isValid() && property.hasNotifySignal()) {
++ // Check if we're already watching this property.
++ const auto pid = property.propertyIndex();
++ Q_ASSERT(pid != -1);
++ auto it = m_trackedProperties.find(pid);
++ const bool found = (it != m_trackedProperties.end());
++ QQuick3DObject *oldObj = nullptr;
++ if (!found) {
++ QQuick3DPropertyWatcher *watcher = new QQuick3DPropertyWatcher(this, property);
++ it = m_trackedProperties.insert(pid, Tracked { watcher, object, pid });
++ } else {
++ oldObj = it->object;
++ it->object = object;
++ }
++
++ if (hint == DirtyPropertyHint::Reference) {
++ const auto &sm = QQuick3DObjectPrivate::get(m_owner)->sceneManager;
++
++ // First check if the object changed
++ const bool changed = (oldObj != object);
++
++ // Deref old object
++ if (changed && oldObj) {
++ QQuick3DObjectPrivate::get(oldObj)->derefSceneManager();
++ QObject::disconnect(oldObj, &QObject::destroyed, m_owner, nullptr);
++ }
++
++ // Ref new object
++ if (changed && object) {
++ QQuick3DObjectPrivate::get(object)->refSceneManager(*sm);
++ QObject::connect(object, &QObject::destroyed, m_owner, [this, property](QObject *obj) {
++ Q_UNUSED(obj);
++ addPropertyWatcher(property, DirtyPropertyHint::Reference, nullptr);
++ });
++ }
++ }
++
++ m_dirtyProperties.push_back(property.propertyIndex());
++ markTrackedPropertyDirty(property, hint);
++ }
++ }
++
++ void QQuick3DPropertyChangedTracker::markTrackedPropertyDirty(QMetaProperty property, DirtyPropertyHint hint)
++ {
++ Q_UNUSED(property);
++ Q_UNUSED(hint);
++ QSSG_CHECK_X(false, "QQuick3DPropertyChangedTracker::onPropertyDirty implementation missing");
++ }
++
++ QQuick3DPropertyWatcher::QQuick3DPropertyWatcher(QQuick3DPropertyChangedTracker *tracker, QMetaProperty property)
++ : m_tracker(tracker)
++ , m_property(property)
++ {
++ Q_ASSERT(tracker != nullptr);
++ Q_ASSERT(property.isValid() && property.hasNotifySignal());
++
++ // Ensure that the watcher is on the watched object's thread if it's not already there.
++ // If not, the watcher will be created on the render thread and the notify connections
++ // will be queued to the render thread, which is not what we want
++ // (as can end-up concurrently mutating the dirty lists).
++ if (thread() != tracker->m_owner->thread())
++ moveToThread(tracker->m_owner->thread());
++
++ const bool isPointerType = (property.metaType().flags().testFlag(QMetaType::IsPointer));
++
++ if (!isPointerType) {
++ // Value change notification
++ const auto idx = staticMetaObject.indexOfSlot("onValuePropertyChanged()");
++ if (QSSG_GUARD_X(idx != -1, "Method not found!")) {
++ auto onPropertyChangedMethod = staticMetaObject.method(idx);
++ connect(m_tracker->m_owner, property.notifySignal(), this, onPropertyChangedMethod);
++ }
++ } else {
++ // Pointer value change notification
++ const auto idx = staticMetaObject.indexOfSlot("onPointerPropertyChanged()");
++ if (QSSG_GUARD_X(idx != -1, "Method not found!")) {
++ auto onPointerPropertyChangedMethod = staticMetaObject.method(idx);
++ connect(m_tracker->m_owner, property.notifySignal(), this, onPointerPropertyChangedMethod);
++ }
++ }
++ }
++
++ void QQuick3DPropertyWatcher::onValuePropertyChanged()
++ {
++ m_tracker->m_dirtyProperties.push_back(m_property.propertyIndex());
++ m_tracker->markTrackedPropertyDirty(m_property, QQuick3DPropertyChangedTracker::DirtyPropertyHint::Value);
++ }
++
++ void QQuick3DPropertyWatcher::onPointerPropertyChanged()
++ {
++ m_tracker->m_dirtyProperties.push_back(m_property.propertyIndex());
++ m_tracker->markTrackedPropertyDirty(m_property, QQuick3DPropertyChangedTracker::DirtyPropertyHint::Reference);;
++ }
++
++ /*!
++ \qmltype AddDefine
++ \inherits Command
++ \inqmlmodule QtQuick3D
++ \brief Adds a preprocessor define to the shader compilation for a \l {RenderPass}{pass}.
++ \since 6.11
++
++ The AddDefine type is used to inject a \c {#define} into the shader
++ compilation for a \l RenderPass. This lets you branch shader behavior
++ at compile time depending on which pass is being executed.
++
++ In the shader code, the define can be used with the standard preprocessor
++ \c {#ifdef} / \c {#if} directives:
++ \badcode
++ #ifdef CUSTOM_PASS_MODE
++ // code compiled only when this define is present
++ #endif
++ \endcode
++
++ \qml
++ RenderPass {
++ commands: [
++ ColorAttachment { name: "color0"; target: colorTexture },
++ AddDefine { name: "CUSTOM_PASS_MODE" },
++ AddDefine { name: "OUTPUT_VERSION"; value: 2 }
++ ]
++ }
++ \endqml
++ */
++
++ /*!
++ \qmlproperty string AddDefine::name
++ The name of the preprocessor macro to define. This becomes the identifier
++ used in \c {#ifdef} or \c {#if} checks in the shader code.
++ */
++
++ /*!
++ \qmlproperty int AddDefine::value
++ The integer value assigned to the preprocessor macro. Defaults to \c 1.
++ When non-zero, \c {#if NAME} evaluates to \c true in the shader.
++ */
++
++ QQuick3DShaderUtilsRenderPassAddDefine::QQuick3DShaderUtilsRenderPassAddDefine()
++ {
++
++ }
++
++ QQuick3DShaderUtilsRenderPassAddDefine::~QQuick3DShaderUtilsRenderPassAddDefine() = default;
++
++ QByteArray QQuick3DShaderUtilsRenderPassAddDefine::name() const
++ {
++ return command.m_name;
++ }
++
++ void QQuick3DShaderUtilsRenderPassAddDefine::setName(const QByteArray &newName)
++ {
++ if (command.m_name != newName) {
++ command.m_name = newName;
++ emit nameChanged();
++ emit changed();
++ }
++ }
++
++ int QQuick3DShaderUtilsRenderPassAddDefine::value() const
++ {
++ return command.m_value;
++ }
++
++ void QQuick3DShaderUtilsRenderPassAddDefine::setValue(int newValue)
++ {
++ if (command.m_value != newValue) {
++ command.m_value = newValue;
++ emit valueChanged();
++ emit changed();
++ }
++ }
++
++ QSSGCommand *QQuick3DShaderUtilsRenderPassAddDefine::cloneCommand() {
++ QSSGAddShaderDefine *cmd = new QSSGAddShaderDefine(command);
++ return cmd;
++ }
++
++ /*!
++ \qmltype SubRenderPass
++ \inherits Command
++ \inqmlmodule QtQuick3D
++ \brief Renders a sub-section of work within the same render target as the parent RenderPass.
++ \since 6.11
++
++ SubRenderPass is a \l Command that subdivides a \l RenderPass into
++ smaller, independently configured pieces while sharing the same render
++ target. Only the parent \l RenderPass controls which textures are
++ rendered into (via \l ColorAttachment and \l DepthTextureAttachment)
++ and when the pass begins (and therefore when the render target is
++ cleared). A SubRenderPass cannot set its own render target or clear
++ settings — those are always inherited from the parent pass.
++
++ What a SubRenderPass \e can control is \e what gets rendered and
++ \e how: it has its own \l{RenderPass::commands}{commands}, so it can
++ carry a \l RenderablesFilter to select a subset of objects, a
++ \l PipelineStateOverride to change depth/blend state, a different
++ \l{RenderPass::materialMode}{materialMode}, and so on.
++
++ A typical use is a forward-rendering pipeline where a single color
++ pass uses separate SubRenderPasses for the background, opaque
++ objects, and transparent objects. All three sub-passes write into the
++ same color and depth textures, but each has different pipeline state:
++
++ \qml
++ RenderPass {
++ id: mainColorPass
++ commands: [
++ // Render targets are set at the parent RenderPass level only
++ ColorAttachment { name: "color0"; target: colorTexture },
++ DepthTextureAttachment { target: depthTexture },
++
++ // Sub-pass 1: render the skybox background
++ SubRenderPass { renderPass: skyboxSubPass },
++
++ // Sub-pass 2: opaque objects with depth testing and writing
++ SubRenderPass { renderPass: opaqueSubPass },
++
++ // Sub-pass 3: transparent objects with blending, no depth write
++ SubRenderPass { renderPass: transparentSubPass }
++ ]
++ }
++
++ // Sub-passes share mainColorPass's render target
++ RenderPass {
++ id: skyboxSubPass
++ passMode: RenderPass.SkyboxPass
++ }
++
++ RenderPass {
++ id: opaqueSubPass
++ commands: [
++ RenderablesFilter { renderableTypes: RenderablesFilter.Opaque }
++ ]
++ }
++
++ RenderPass {
++ id: transparentSubPass
++ commands: [
++ RenderablesFilter { renderableTypes: RenderablesFilter.Transparent },
++ PipelineStateOverride {
++ blendEnabled: true
++ depthWriteEnabled: false
++ }
++ ]
++ }
++ \endqml
++
++ \sa RenderPass, RenderablesFilter, PipelineStateOverride
++ */
++
++ /*!
++ \qmlproperty RenderPass SubRenderPass::renderPass
++ The RenderPass that will be executed as a sub-pass when this command is processed.
++ */
++
++ QQuick3DShaderUtilsSubRenderPass::~QQuick3DShaderUtilsSubRenderPass()
++ {
++ if (m_renderPass)
++ QQuick3DRenderPassPrivateHelper::subPassDeref(m_renderPass);
++ }
++
++ QSSGCommand *QQuick3DShaderUtilsSubRenderPass::cloneCommand()
++ {
++ QSSGSubRenderPass *cmd = nullptr;
++
++ if (!m_renderPass) {
++ if (!m_hasWarnedAboutInvalidId) {
++ qCWarning(lcSubRenderPass, "SubRenderPass: No render pass specified. Set the 'renderPass' property.");
++ m_hasWarnedAboutInvalidId = true;
++ }
++ return nullptr;
++ }
++
++ QSSGResourceId userPassId = QQuick3DExtensionHelpers::getResourceId(*m_renderPass);
++ // Ensure we have a valid resource id before continuing.
++ if (userPassId != QSSGResourceId::Invalid) {
++ cmd = new QSSGSubRenderPass();
++ cmd->setSubPass(userPassId);
++ // Reset warning flag on success
++ m_hasWarnedAboutInvalidId = false;
++ } else {
++ // Resource ID is not ready yet - this is expected during initialization
++ qCDebug(lcSubRenderPass, "SubRenderPass: Render pass resource ID not yet available, will retry.");
++ update();
++ }
++
++ return cmd;
++ }
++
++ QSSGRenderGraphObject *QQuick3DShaderUtilsSubRenderPass::updateSpatialNode(QSSGRenderGraphObject *node)
++ {
++ return node;
++ }
++
++ void QQuick3DShaderUtilsSubRenderPass::itemChange(ItemChange change, const ItemChangeData &value)
++ {
++ if (change == QQuick3DObject::ItemSceneChange)
++ updateSceneManager(value.sceneManager);
++ }
++
++ void QQuick3DShaderUtilsSubRenderPass::updateSceneManager(QQuick3DSceneManager *sceneManager)
++ {
++ if (sceneManager)
++ QQuick3DObjectPrivate::refSceneManager(m_renderPass, *sceneManager);
++ else
++ QQuick3DObjectPrivate::derefSceneManager(m_renderPass);
++ }
++
++ QQuick3DRenderPass *QQuick3DShaderUtilsSubRenderPass::renderPass() const
++ {
++ return m_renderPass;
++ }
++
++ void QQuick3DShaderUtilsSubRenderPass::setRenderPass(QQuick3DRenderPass *newRenderPass)
++ {
++ if (m_renderPass == newRenderPass)
++ return;
++
++ QQuick3DObjectPrivate::attachWatcher(this, &QQuick3DShaderUtilsSubRenderPass::setRenderPass, newRenderPass, m_renderPass.data());
++
++ if (newRenderPass)
++ QQuick3DRenderPassPrivateHelper::subPassRef(newRenderPass);
++
++ // NOTE: When the watcher calls us because the referenced pass is being
++ // destroyed, the QPointer is already null, so a dying pass is never deref'd.
++ if (m_renderPass)
++ QQuick3DRenderPassPrivateHelper::subPassDeref(m_renderPass);
++
++ m_renderPass = newRenderPass;
++ m_hasWarnedAboutInvalidId = false; // Reset warning flag when property changes
++ emit renderPassChanged();
++ emit changed();
++ }
++
++ /*!
++ \qmltype DepthTextureAttachment
++ \inherits Command
++ \inqmlmodule QtQuick3D
++ \brief Defines a depth texture attachment for a \l {RenderPass}{pass}.
++ \since 6.11
++
++ DepthTextureAttachment attaches a \l RenderPassTexture with a depth
++ format as the depth buffer for a \l RenderPass. Unlike
++ \l DepthStencilAttachment (which uses an opaque render buffer), the
++ resulting depth data is stored in a texture that can be read by
++ subsequent passes via \l RenderOutputProvider.
++
++ \qml
++ RenderPassTexture {
++ id: depthTexture
++ format: RenderPassTexture.Depth24
++ }
++
++ RenderPass {
++ commands: [
++ ColorAttachment { name: "color0"; target: colorTexture },
++ DepthTextureAttachment { target: depthTexture }
++ ]
++ }
++
++ // Expose the depth texture to a material via RenderOutputProvider
++ Texture {
++ textureProvider: RenderOutputProvider {
++ textureSource: RenderOutputProvider.UserPassTexture
++ renderPass: myRenderPass
++ }
++ }
++ \endqml
++
++ \sa RenderPassTexture, DepthStencilAttachment, RenderOutputProvider
++ */
++
++ /*!
++ \qmlproperty RenderPassTexture DepthTextureAttachment::target
++ The \l RenderPassTexture that will be used as the depth attachment. The texture
++ must have a depth-compatible format (e.g. \c Depth16, \c Depth24, \c Depth32,
++ or \c Depth24Stencil8).
++ */
++
++ QQuick3DShaderUtilsRenderPassDepthTextureAttachment::~QQuick3DShaderUtilsRenderPassDepthTextureAttachment()
++ {
++
++ }
++
++ QSSGCommand *QQuick3DShaderUtilsRenderPassDepthTextureAttachment::cloneCommand()
++ {
++ if (target) {
++ QSSGDepthTextureAttachment *cmd = new QSSGDepthTextureAttachment(QByteArrayLiteral("__depth__"));
++ cmd->m_textureCmd = target->command;
++ return cmd;
++ }
++
++ return nullptr;
++ }
++
++ QQuick3DShaderUtilsRenderPassDepthStencilAttachment::~QQuick3DShaderUtilsRenderPassDepthStencilAttachment()
++ {
++
++ }
++
++ QSSGCommand *QQuick3DShaderUtilsRenderPassDepthStencilAttachment::cloneCommand()
++ {
++ QSSGDepthStencilAttachment *cmd = new QSSGDepthStencilAttachment;
++ return cmd;
++ }
++
++ QT_END_NAMESPACE
++