// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "qshaderdescription_p.h"
#include "qshader_p.h"
#include <QDebug>
#include <QDataStream>
#include <QJsonObject>
#include <QJsonArray>

QT_BEGIN_NAMESPACE

/*!
    \class QShaderDescription
    \ingroup painting-3D
    \inmodule QtGui
    \since 6.6

    \brief Describes the interface of a shader.

    \warning The QRhi family of classes in the Qt Gui module, including QShader
    and QShaderDescription, offer limited compatibility guarantees. There are
    no source or binary compatibility guarantees for these classes, meaning the
    API is only guaranteed to work with the Qt version the application was
    developed against. Source incompatible changes are however aimed to be kept
    at a minimum and will only be made in minor releases (6.7, 6.8, and so on).
    To use these classes in an application, link to
    \c{Qt::GuiPrivate} (if using CMake), and include the headers with the \c
    rhi prefix, for example \c{#include <rhi/qshaderdescription.h>}.

    A shader typically has a set of inputs and outputs. A vertex shader for
    example has a number of input variables and may use one or more uniform
    buffers to access data (e.g. a modelview matrix) provided by the
    application. The shader for the fragment stage receives data from the
    vertex stage (in a simple setup) and may also rely on data from uniform
    buffers, images, and samplers.

    When it comes to vertex inputs and the layout of the uniform buffers (what
    are the names of the members? what is there size, offset, and so on),
    applications and frameworks may need to discover this dynamically at run
    time. This is typical when the shader is not built-in but provided by an
    external entity, like the user.

    Modern and lean graphics APIs may no longer provide a way to query shader
    reflection information at run time. Therefore, such data is now
    automatically generated by QShaderBaker and is provided as a
    QShaderDescription object for each and every QShader.

    \section2 Example

    Take the following vertex shader:

    \badcode
        #version 440

        layout(location = 0) in vec4 position;
        layout(location = 1) in vec3 color;
        layout(location = 0) out vec3 v_color;

        layout(std140, binding = 0) uniform buf {
            mat4 mvp;
            float opacity;
        } ubuf;

        void main()
        {
            v_color = color;
            gl_Position = ubuf.mvp * position;
        }
    \endcode

    This shader has two inputs: \c position at location 0 with a type of \c
    vec4, and \c color at location 1 with a type of \c vec3. It has one output:
    \c v_color, although this is typically not interesting for applications.
    What is more important, there is a uniform block at binding 0 with a size
    of 68 bytes and two members, a 4x4 matrix named \c mvp at offset 0, and a
    float \c opacity at offset 64.

    All this is described by a QShaderDescription object. QShaderDescription can
    be serialized to JSON and to a binary format via QDataStream, and can be
    deserialized from this binary format. In practice this is rarely needed
    since QShader takes care of the associated QShaderDescription automatically,
    but if the QShaderDescription of the above shader would be written out as
    JSON (like it is done by the \c qsb tool's \c{-d} option), it would look
    like the following:

    \badcode
        {
            "inputs": [
                {
                    "location": 1,
                    "name": "color",
                    "type": "vec3"
                },
                {
                    "location": 0,
                    "name": "position",
                    "type": "vec4"
                }
            ],
            "outputs": [
                {
                    "location": 0,
                    "name": "v_color",
                    "type": "vec3"
                }
            ],
            "uniformBlocks": [
                {
                    "binding": 0,
                    "blockName": "buf",
                    "members": [
                        {
                            "matrixStride": 16,
                            "name": "mvp",
                            "offset": 0,
                            "size": 64,
                            "type": "mat4"
                        },
                        {
                            "name": "opacity",
                            "offset": 64,
                            "size": 4,
                            "type": "float"
                        }
                    ],
                    "set": 0,
                    "size": 68,
                    "structName": "ubuf"
                }
            ]
        }
    \endcode

    The C++ API allows accessing a data structure like the above. For
    simplicity the inner structs only contain public data members, also
    considering that their layout is unlikely to change in the future.

    \sa QShaderBaker, QShader
 */

/*!
    \enum QShaderDescription::VariableType
    Represents the type of a variable or block member.

    \value Unknown
    \value Float
    \value Vec2
    \value Vec3
    \value Vec4
    \value Mat2
    \value Mat2x3
    \value Mat2x4
    \value Mat3
    \value Mat3x2
    \value Mat3x4
    \value Mat4
    \value Mat4x2
    \value Mat4x3
    \value Int
    \value Int2
    \value Int3
    \value Int4
    \value Uint
    \value Uint2
    \value Uint3
    \value Uint4
    \value Bool
    \value Bool2
    \value Bool3
    \value Bool4
    \value Double
    \value Double2
    \value Double3
    \value Double4
    \value DMat2
    \value DMat2x3
    \value DMat2x4
    \value DMat3
    \value DMat3x2
    \value DMat3x4
    \value DMat4
    \value DMat4x2
    \value DMat4x3
    \value Sampler1D
    \value Sampler2D
    \value Sampler2DMS
    \value Sampler3D
    \value SamplerCube
    \value Sampler1DArray
    \value Sampler2DArray
    \value Sampler2DMSArray
    \value Sampler3DArray
    \value SamplerCubeArray
    \value SamplerRect
    \value SamplerBuffer
    \value SamplerExternalOES
    \value Sampler For separate samplers.
    \value Image1D
    \value Image2D
    \value Image2DMS
    \value Image3D
    \value ImageCube
    \value Image1DArray
    \value Image2DArray
    \value Image2DMSArray
    \value Image3DArray
    \value ImageCubeArray
    \value ImageRect
    \value ImageBuffer
    \value Struct
    \value Half
    \value Half2
    \value Half3
    \value Half4
 */

/*!
    \enum QShaderDescription::ImageFormat
    Image format.

    \value ImageFormatUnknown
    \value ImageFormatRgba32f
    \value ImageFormatRgba16f
    \value ImageFormatR32f
    \value ImageFormatRgba8
    \value ImageFormatRgba8Snorm
    \value ImageFormatRg32f
    \value ImageFormatRg16f
    \value ImageFormatR11fG11fB10f
    \value ImageFormatR16f
    \value ImageFormatRgba16
    \value ImageFormatRgb10A2
    \value ImageFormatRg16
    \value ImageFormatRg8
    \value ImageFormatR16
    \value ImageFormatR8
    \value ImageFormatRgba16Snorm
    \value ImageFormatRg16Snorm
    \value ImageFormatRg8Snorm
    \value ImageFormatR16Snorm
    \value ImageFormatR8Snorm
    \value ImageFormatRgba32i
    \value ImageFormatRgba16i
    \value ImageFormatRgba8i
    \value ImageFormatR32i
    \value ImageFormatRg32i
    \value ImageFormatRg16i
    \value ImageFormatRg8i
    \value ImageFormatR16i
    \value ImageFormatR8i
    \value ImageFormatRgba32ui
    \value ImageFormatRgba16ui
    \value ImageFormatRgba8ui
    \value ImageFormatR32ui
    \value ImageFormatRgb10a2ui
    \value ImageFormatRg32ui
    \value ImageFormatRg16ui
    \value ImageFormatRg8ui
    \value ImageFormatR16ui
    \value ImageFormatR8ui
 */

/*!
    \enum QShaderDescription::ImageFlag
    Image flags.

    \value ReadOnlyImage
    \value WriteOnlyImage
 */

/*!
    \enum QShaderDescription::QualifierFlag
    Qualifier flags.

    \value QualifierReadOnly
    \value QualifierWriteOnly
    \value QualifierCoherent
    \value QualifierVolatile
    \value QualifierRestrict
 */

/*!
    \struct QShaderDescription::InOutVariable
    \inmodule QtGui
    \since 6.6

    \brief Describes an input or output variable in the shader.

    \note This is a RHI API with limited compatibility guarantees, see \l QShaderDescription
    for details.
 */

/*!
    \variable QShaderDescription::InOutVariable::name
 */

/*!
    \variable QShaderDescription::InOutVariable::type
 */

/*!
    \variable QShaderDescription::InOutVariable::location
 */

/*!
    \variable QShaderDescription::InOutVariable::binding
 */

/*!
    \variable QShaderDescription::InOutVariable::descriptorSet
 */

/*!
    \variable QShaderDescription::InOutVariable::imageFormat
 */

/*!
    \variable QShaderDescription::InOutVariable::imageFlags
 */

/*!
    \variable QShaderDescription::InOutVariable::arrayDims
 */

/*!
    \variable QShaderDescription::InOutVariable::perPatch
 */

/*!
    \variable QShaderDescription::InOutVariable::structMembers
 */

/*!
    \struct QShaderDescription::BlockVariable
    \inmodule QtGui
    \since 6.6

    \brief Describes a member of a uniform or push constant block.

    \note This is a RHI API with limited compatibility guarantees, see \l QShaderDescription
    for details.
 */

/*!
    \variable QShaderDescription::BlockVariable::name
 */

/*!
    \variable QShaderDescription::BlockVariable::type
 */

/*!
    \variable QShaderDescription::BlockVariable::offset
 */

/*!
    \variable QShaderDescription::BlockVariable::size
 */

/*!
    \variable QShaderDescription::BlockVariable::arrayDims
 */

/*!
    \variable QShaderDescription::BlockVariable::arrayStride
 */

/*!
    \variable QShaderDescription::BlockVariable::matrixStride
 */

/*!
    \variable QShaderDescription::BlockVariable::matrixIsRowMajor
 */

/*!
    \variable QShaderDescription::BlockVariable::structMembers
 */

/*!
    \struct QShaderDescription::UniformBlock
    \inmodule QtGui
    \since 6.6

    \brief Describes a uniform block.

    \note When translating to shading languages without uniform block support
    (like GLSL 120 or GLSL/ES 100), uniform blocks are replaced with ordinary
    uniforms in a struct. The name of the struct, and so the prefix for the
    uniforms generated from the block members, is given by structName.

    \note This is a RHI API with limited compatibility guarantees, see \l QShaderDescription
    for details.
 */

/*!
    \variable QShaderDescription::UniformBlock::blockName
 */

/*!
    \variable QShaderDescription::UniformBlock::structName
 */

/*!
    \variable QShaderDescription::UniformBlock::size
 */

/*!
    \variable QShaderDescription::UniformBlock::binding
 */

/*!
    \variable QShaderDescription::UniformBlock::descriptorSet
 */

/*!
    \variable QShaderDescription::UniformBlock::members
 */

/*!
    \struct QShaderDescription::PushConstantBlock
    \inmodule QtGui
    \since 6.6

    \brief Describes a push constant block.

    \note This is a RHI API with limited compatibility guarantees, see \l QShaderDescription
    for details.
 */

/*!
    \variable QShaderDescription::PushConstantBlock::name
 */

/*!
    \variable QShaderDescription::PushConstantBlock::size
 */

/*!
    \variable QShaderDescription::PushConstantBlock::members
 */

/*!
    \struct QShaderDescription::StorageBlock
    \inmodule QtGui
    \since 6.6

    \brief Describes a shader storage block.

    \note This is a RHI API with limited compatibility guarantees, see \l QShaderDescription
    for details.
 */

/*!
    \variable QShaderDescription::StorageBlock::blockName
 */

/*!
    \variable QShaderDescription::StorageBlock::instanceName
 */

/*!
    \variable QShaderDescription::StorageBlock::knownSize
 */

/*!
    \variable QShaderDescription::StorageBlock::binding
 */

/*!
    \variable QShaderDescription::StorageBlock::descriptorSet
 */

/*!
    \variable QShaderDescription::StorageBlock::members
 */

/*!
    \variable QShaderDescription::StorageBlock::runtimeArrayStride
 */

/*!
    \variable QShaderDescription::StorageBlock::qualifierFlags
 */

/*!
    \struct QShaderDescription::BuiltinVariable
    \inmodule QtGui
    \since 6.6

    \brief Describes a built-in variable.

    \note This is a RHI API with limited compatibility guarantees, see \l QShaderDescription
    for details.
 */

/*!
    \variable QShaderDescription::BuiltinVariable::type
 */

/*!
    \variable QShaderDescription::BuiltinVariable::varType
 */

/*!
    \variable QShaderDescription::BuiltinVariable::arrayDims
 */

/*!
    \enum QShaderDescription::BuiltinType
    Built-in variable type.

    \value PositionBuiltin
    \value PointSizeBuiltin
    \value ClipDistanceBuiltin
    \value CullDistanceBuiltin
    \value VertexIdBuiltin
    \value InstanceIdBuiltin
    \value PrimitiveIdBuiltin
    \value InvocationIdBuiltin
    \value LayerBuiltin
    \value ViewportIndexBuiltin
    \value TessLevelOuterBuiltin
    \value TessLevelInnerBuiltin
    \value TessCoordBuiltin
    \value PatchVerticesBuiltin
    \value FragCoordBuiltin
    \value PointCoordBuiltin
    \value FrontFacingBuiltin
    \value SampleIdBuiltin
    \value SamplePositionBuiltin
    \value SampleMaskBuiltin
    \value FragDepthBuiltin
    \value NumWorkGroupsBuiltin
    \value WorkgroupSizeBuiltin
    \value WorkgroupIdBuiltin
    \value LocalInvocationIdBuiltin
    \value GlobalInvocationIdBuiltin
    \value LocalInvocationIndexBuiltin
    \value VertexIndexBuiltin
    \value InstanceIndexBuiltin
 */

/*!
    Constructs a new, empty QShaderDescription.

    \note Being empty implies that isValid() returns \c false for the
    newly constructed instance.
 */
QShaderDescription::QShaderDescription()
    : d(new QShaderDescriptionPrivate)
{
}

/*!
    \internal
 */
void QShaderDescription::detach()
{
    qAtomicDetach(d);
}

/*!
    Constructs a copy of \a other.
 */
QShaderDescription::QShaderDescription(const QShaderDescription &other)
    : d(other.d)
{
    d->ref.ref();
}

/*!
    Assigns \a other to this object.
 */
QShaderDescription &QShaderDescription::operator=(const QShaderDescription &other)
{
    qAtomicAssign(d, other.d);
    return *this;
}

/*!
    Destructor.
 */
QShaderDescription::~QShaderDescription()
{
    if (!d->ref.deref())
        delete d;
}

/*!
   \return true if the QShaderDescription contains at least one entry in one of
   the variable and block lists.
 */
bool QShaderDescription::isValid() const
{
    return !d->inVars.isEmpty() || !d->outVars.isEmpty()
        || !d->uniformBlocks.isEmpty() || !d->pushConstantBlocks.isEmpty() || !d->storageBlocks.isEmpty()
        || !d->combinedImageSamplers.isEmpty() || !d->storageImages.isEmpty()
        || !d->separateImages.isEmpty() || !d->separateSamplers.isEmpty()
        || !d->inBuiltins.isEmpty() || !d->outBuiltins.isEmpty();
}

/*!
    \return a serialized JSON text version of the data.

    \note There is no deserialization method provided for JSON text.

    \sa serialize()
 */
QByteArray QShaderDescription::toJson() const
{
    return d->makeDoc().toJson();
}

/*!
    Serializes this QShaderDescription to \a stream. \a version specifies
    the qsb version.

    \sa deserialize(), toJson()
 */
void QShaderDescription::serialize(QDataStream *stream, int version) const
{
    d->writeToStream(stream, version);
}

/*!
    \return a new QShaderDescription loaded from \a stream. \a version specifies
    the qsb version.

    \sa serialize()
 */
QShaderDescription QShaderDescription::deserialize(QDataStream *stream, int version)
{
    QShaderDescription desc;
    QShaderDescriptionPrivate::get(&desc)->loadFromStream(stream, version);
    return desc;
}

/*!
    \return the list of input variables. This includes vertex inputs (sometimes
    called attributes) for the vertex stage, and inputs for other stages
    (sometimes called varyings).
 */
QList<QShaderDescription::InOutVariable> QShaderDescription::inputVariables() const
{
    return d->inVars;
}

/*!
    \return the list of output variables.
 */
QList<QShaderDescription::InOutVariable> QShaderDescription::outputVariables() const
{
    return d->outVars;
}

/*!
    \return the list of uniform blocks.
 */
QList<QShaderDescription::UniformBlock> QShaderDescription::uniformBlocks() const
{
    return d->uniformBlocks;
}

/*!
    \return the list of push constant blocks.

    \note Avoid relying on push constant blocks for shaders that are to be used
    in combination with the Qt Rendering Hardware Interface since that
    currently has no support for them.
 */
QList<QShaderDescription::PushConstantBlock> QShaderDescription::pushConstantBlocks() const
{
    return d->pushConstantBlocks;
}

/*!
    \return the list of shader storage blocks.

    For example, with GLSL/Vulkan shaders as source, the declaration

    \badcode
        struct Stuff {
            vec2 a;
            vec2 b;
        };
        layout(std140, binding = 0) buffer StuffSsbo {
            vec4 whatever;
            Stuff stuff[];
        } buf;
    \endcode

    generates the following: (shown as textual JSON here)

    \badcode
        "storageBlocks": [ {
            "binding": 0,
            "blockName": "StuffSsbo",
            "instanceName": "buf",
            "knownSize": 16,
            "runtimeArrayStride": 16
            "members": [
                {
                    "name": "whatever",
                    "offset": 0,
                    "size": 16,
                    "type": "vec4"
                },
                {
                    "arrayDims": [
                        0
                    ],
                    "name": "stuff",
                    "offset": 16,
                    "size": 0,
                    "structMembers": [
                        {
                            "name": "a",
                            "offset": 0,
                            "size": 8,
                            "type": "vec2"
                        },
                        {
                            "name": "b",
                            "offset": 8,
                            "size": 8,
                            "type": "vec2"
                        }
                    ],
                    "type": "struct"
                }
            ],
            "set": 0
        } ]
    \endcode

    \note The size of the last member in the storage block is undefined. This shows
    up as \c size 0 and an array dimension of \c{[0]}. The storage block's \c knownSize
    excludes the size of the last member since that will only be known at run time. The
    stride in bytes between array items for a last member with undefined array size is
    \c runtimeArrayStride.  This value is determined according to the specified buffer
    memory layout standard (std140, std430) rules.

    \note SSBOs are not available with some graphics APIs, such as, OpenGL 2.x or
    OpenGL ES older than 3.1.
 */
QList<QShaderDescription::StorageBlock> QShaderDescription::storageBlocks() const
{
    return d->storageBlocks;
}

/*!
    \return the list of combined image samplers

    With GLSL/Vulkan shaders as source a \c{layout(binding = 1) uniform sampler2D tex;}
    uniform generates the following: (shown as textual JSON here)

    \badcode
       "combinedImageSamplers": [
            {
                "binding": 1,
                "name": "tex",
                "set": 0,
                "type": "sampler2D"
            }
        ]
    \endcode

    This does not mean that other language versions of the shader must also use
    a combined image sampler, especially considering that the concept may not
    exist everywhere. For instance, a HLSL version will likely just use a
    Texture2D and SamplerState object with registers t1 and s1, respectively.
  */
QList<QShaderDescription::InOutVariable> QShaderDescription::combinedImageSamplers() const
{
    return d->combinedImageSamplers;
}

QList<QShaderDescription::InOutVariable> QShaderDescription::separateImages() const
{
    return d->separateImages;
}

QList<QShaderDescription::InOutVariable> QShaderDescription::separateSamplers() const
{
    return d->separateSamplers;
}

/*!
    \return the list of image variables.

    These will likely occur in compute shaders. For example,
    \c{layout (binding = 0, rgba8) uniform readonly image2D inputImage;}
    generates the following: (shown as textual JSON here)

    \badcode
       "storageImages": [
            {
                "binding": 0,
                "imageFormat": "rgba8",
                "name": "inputImage",
                "set": 0,
                "type": "image2D"
            }
        ]
    \endcode

    \note Separate image objects are not compatible with some graphics APIs,
    such as, OpenGL 2.x or OpenGL ES older than 3.1.
  */
QList<QShaderDescription::InOutVariable> QShaderDescription::storageImages() const
{
    return d->storageImages;
}

/*!
    \return the list of active builtins used as input. For example, a
    tessellation evaluation shader reading the value of gl_TessCoord and
    gl_Position will have TessCoordBuiltin and PositionBuiltin listed here.
 */
QVector<QShaderDescription::BuiltinVariable> QShaderDescription::inputBuiltinVariables() const
{
    return d->inBuiltins;
}

/*!
    \return the list of active built-in variables used as input. For example, a
    vertex shader will very often have PositionBuiltin as an output built-in.
 */
QVector<QShaderDescription::BuiltinVariable> QShaderDescription::outputBuiltinVariables() const
{
    return d->outBuiltins;
}

/*!
    \return the local size of a compute shader.

    For example, for a compute shader with the following declaration the
    function returns { 256, 16, 1}.

    \badcode
        layout(local_size_x = 256, local_size_y = 16, local_size_z = 1) in;
    \endcode
 */
std::array<uint, 3> QShaderDescription::computeShaderLocalSize() const
{
    return d->localSize;
}

/*!
    \return the number of output vertices.

    For example, for a tessellation control shader with the following
    declaration the function returns 3.

    \badcode
        layout(vertices = 3) out;
    \endcode
 */
uint QShaderDescription::tessellationOutputVertexCount() const
{
    return d->tessOutVertCount;
}

/*!
    \enum QShaderDescription::TessellationMode

    \value UnknownTessellationMode
    \value TrianglesTessellationMode
    \value QuadTessellationMode
    \value IsolineTessellationMode
 */

/*!
    \return the tessellation execution mode for a tessellation control or
    evaluation shader.

    When not set, the returned value is UnknownTessellationMode.

    For example, for a tessellation evaluation shader with the following
    declaration the function returns TrianglesTessellationMode.

    \badcode
        layout(triangles) in;
    \endcode
 */
QShaderDescription::TessellationMode QShaderDescription::tessellationMode() const
{
    return d->tessMode;
}

/*!
    \enum QShaderDescription::TessellationWindingOrder

    \value UnknownTessellationWindingOrder
    \value CwTessellationWindingOrder
    \value CcwTessellationWindingOrder
 */

/*!
    \return the tessellation winding order for a tessellation control or
    evaluation shader.

    When not set, the returned value is UnknownTessellationWindingOrder.

    For example, for a tessellation evaluation shader with the following
    declaration the function returns CcwTessellationWindingOrder.

    \badcode
        layout(triangles, fractional_odd_spacing, ccw) in;
    \endcode
 */
QShaderDescription::TessellationWindingOrder QShaderDescription::tessellationWindingOrder() const
{
    return d->tessWind;
}

/*!
    \enum QShaderDescription::TessellationPartitioning

    \value UnknownTessellationPartitioning
    \value EqualTessellationPartitioning
    \value FractionalEvenTessellationPartitioning
    \value FractionalOddTessellationPartitioning
 */

/*!
    \return the tessellation partitioning mode for a tessellation control or
    evaluation shader.

    When not set, the returned value is UnknownTessellationPartitioning.

    For example, for a tessellation evaluation shader with the following
    declaration the function returns FractionalOddTessellationPartitioning.

    \badcode
        layout(triangles, fractional_odd_spacing, ccw) in;
    \endcode
 */
QShaderDescription::TessellationPartitioning QShaderDescription::tessellationPartitioning() const
{
    return d->tessPart;
}

static const struct TypeTab {
    const char k[20];
    QShaderDescription::VariableType v;
} typeTab[] = {
    { "float", QShaderDescription::Float },
    { "vec2", QShaderDescription::Vec2 },
    { "vec3", QShaderDescription::Vec3 },
    { "vec4", QShaderDescription::Vec4 },
    { "mat2", QShaderDescription::Mat2 },
    { "mat3", QShaderDescription::Mat3 },
    { "mat4", QShaderDescription::Mat4 },

    { "struct", QShaderDescription::Struct },

    { "sampler1D", QShaderDescription::Sampler1D },
    { "sampler2D", QShaderDescription::Sampler2D },
    { "sampler2DMS", QShaderDescription::Sampler2DMS },
    { "sampler3D", QShaderDescription::Sampler3D },
    { "samplerCube", QShaderDescription::SamplerCube },
    { "sampler1DArray", QShaderDescription::Sampler1DArray },
    { "sampler2DArray", QShaderDescription::Sampler2DArray },
    { "sampler2DMSArray", QShaderDescription::Sampler2DMSArray },
    { "sampler3DArray", QShaderDescription::Sampler3DArray },
    { "samplerCubeArray", QShaderDescription::SamplerCubeArray },
    { "samplerRect", QShaderDescription::SamplerRect },
    { "samplerBuffer", QShaderDescription::SamplerBuffer },
    { "samplerExternalOES", QShaderDescription::SamplerExternalOES },
    { "sampler", QShaderDescription::Sampler },

    { "mat2x3", QShaderDescription::Mat2x3 },
    { "mat2x4", QShaderDescription::Mat2x4 },
    { "mat3x2", QShaderDescription::Mat3x2 },
    { "mat3x4", QShaderDescription::Mat3x4 },
    { "mat4x2", QShaderDescription::Mat4x2 },
    { "mat4x3", QShaderDescription::Mat4x3 },

    { "int", QShaderDescription::Int },
    { "ivec2", QShaderDescription::Int2 },
    { "ivec3", QShaderDescription::Int3 },
    { "ivec4", QShaderDescription::Int4 },

    { "uint", QShaderDescription::Uint },
    { "uvec2", QShaderDescription::Uint2 },
    { "uvec3", QShaderDescription::Uint3 },
    { "uvec4", QShaderDescription::Uint4 },

    { "bool", QShaderDescription::Bool },
    { "bvec2", QShaderDescription::Bool2 },
    { "bvec3", QShaderDescription::Bool3 },
    { "bvec4", QShaderDescription::Bool4 },

    { "double", QShaderDescription::Double },
    { "dvec2", QShaderDescription::Double2 },
    { "dvec3", QShaderDescription::Double3 },
    { "dvec4", QShaderDescription::Double4 },
    { "dmat2", QShaderDescription::DMat2 },
    { "dmat3", QShaderDescription::DMat3 },
    { "dmat4", QShaderDescription::DMat4 },
    { "dmat2x3", QShaderDescription::DMat2x3 },
    { "dmat2x4", QShaderDescription::DMat2x4 },
    { "dmat3x2", QShaderDescription::DMat3x2 },
    { "dmat3x4", QShaderDescription::DMat3x4 },
    { "dmat4x2", QShaderDescription::DMat4x2 },
    { "dmat4x3", QShaderDescription::DMat4x3 },

    { "image1D", QShaderDescription::Image1D },
    { "image2D", QShaderDescription::Image2D },
    { "image2DMS", QShaderDescription::Image2DMS },
    { "image3D", QShaderDescription::Image3D },
    { "imageCube", QShaderDescription::ImageCube },
    { "image1DArray", QShaderDescription::Image1DArray },
    { "image2DArray", QShaderDescription::Image2DArray },
    { "image2DMSArray", QShaderDescription::Image2DMSArray },
    { "image3DArray", QShaderDescription::Image3DArray },
    { "imageCubeArray", QShaderDescription::ImageCubeArray },
    { "imageRect", QShaderDescription::ImageRect },
    { "imageBuffer", QShaderDescription::ImageBuffer },

    { "half", QShaderDescription::Half },
    { "half2", QShaderDescription::Half2 },
    { "half3", QShaderDescription::Half3 },
    { "half4", QShaderDescription::Half4 } };

static QLatin1StringView typeStr(QShaderDescription::VariableType t)
{
    for (size_t i = 0; i < sizeof(typeTab) / sizeof(TypeTab); ++i) {
        if (typeTab[i].v == t)
            return QLatin1StringView(typeTab[i].k);
    }
    return {};
}

static const struct ImageFormatTab {
    const char k[15];
    QShaderDescription::ImageFormat v;
} imageFormatTab[] {
    { "unknown", QShaderDescription::ImageFormatUnknown },
    { "rgba32f", QShaderDescription::ImageFormatRgba32f },
    { "rgba16", QShaderDescription::ImageFormatRgba16f },
    { "r32f", QShaderDescription::ImageFormatR32f },
    { "rgba8", QShaderDescription::ImageFormatRgba8 },
    { "rgba8_snorm", QShaderDescription::ImageFormatRgba8Snorm },
    { "rg32f", QShaderDescription::ImageFormatRg32f },
    { "rg16f", QShaderDescription::ImageFormatRg16f },
    { "r11f_g11f_b10f", QShaderDescription::ImageFormatR11fG11fB10f },
    { "r16f", QShaderDescription::ImageFormatR16f },
    { "rgba16", QShaderDescription::ImageFormatRgba16 },
    { "rgb10_a2", QShaderDescription::ImageFormatRgb10A2 },
    { "rg16", QShaderDescription::ImageFormatRg16 },
    { "rg8", QShaderDescription::ImageFormatRg8 },
    { "r16", QShaderDescription::ImageFormatR16 },
    { "r8", QShaderDescription::ImageFormatR8 },
    { "rgba16_snorm", QShaderDescription::ImageFormatRgba16Snorm },
    { "rg16_snorm", QShaderDescription::ImageFormatRg16Snorm },
    { "rg8_snorm", QShaderDescription::ImageFormatRg8Snorm },
    { "r16_snorm", QShaderDescription::ImageFormatR16Snorm },
    { "r8_snorm", QShaderDescription::ImageFormatR8Snorm },
    { "rgba32i", QShaderDescription::ImageFormatRgba32i },
    { "rgba16i", QShaderDescription::ImageFormatRgba16i },
    { "rgba8i", QShaderDescription::ImageFormatRgba8i },
    { "r32i", QShaderDescription::ImageFormatR32i },
    { "rg32i", QShaderDescription::ImageFormatRg32i },
    { "rg16i", QShaderDescription::ImageFormatRg16i },
    { "rg8i", QShaderDescription::ImageFormatRg8i },
    { "r16i", QShaderDescription::ImageFormatR16i },
    { "r8i", QShaderDescription::ImageFormatR8i },
    { "rgba32ui", QShaderDescription::ImageFormatRgba32ui },
    { "rgba16ui", QShaderDescription::ImageFormatRgba16ui },
    { "rgba8ui", QShaderDescription::ImageFormatRgba8ui },
    { "r32ui", QShaderDescription::ImageFormatR32ui },
    { "rgb10_a2ui", QShaderDescription::ImageFormatRgb10a2ui },
    { "rg32ui", QShaderDescription::ImageFormatRg32ui },
    { "rg16ui", QShaderDescription::ImageFormatRg16ui },
    { "rg8ui", QShaderDescription::ImageFormatRg8ui },
    { "r16ui", QShaderDescription::ImageFormatR16ui },
    { "r8ui", QShaderDescription::ImageFormatR8ui }
};

static QLatin1StringView imageFormatStr(QShaderDescription::ImageFormat f)
{
    for (size_t i = 0; i < sizeof(imageFormatTab) / sizeof(ImageFormatTab); ++i) {
        if (imageFormatTab[i].v == f)
            return QLatin1StringView(imageFormatTab[i].k);
    }
    return {};
}

static const struct BuiltinTypeTab {
    const char k[21];
    QShaderDescription::BuiltinType v;
} builtinTypeTab[] = {
    { "Position", QShaderDescription::PositionBuiltin },
    { "PointSize", QShaderDescription::PointSizeBuiltin },
    { "ClipDistance", QShaderDescription::ClipDistanceBuiltin },
    { "CullDistance", QShaderDescription::CullDistanceBuiltin },
    { "VertexId", QShaderDescription::VertexIdBuiltin },
    { "InstanceId", QShaderDescription::InstanceIdBuiltin },
    { "PrimitiveId", QShaderDescription::PrimitiveIdBuiltin },
    { "InvocationId", QShaderDescription::InvocationIdBuiltin },
    { "Layer", QShaderDescription::LayerBuiltin },
    { "ViewportIndex", QShaderDescription::ViewportIndexBuiltin },
    { "TessLevelOuter", QShaderDescription::TessLevelOuterBuiltin },
    { "TessLevelInner", QShaderDescription::TessLevelInnerBuiltin },
    { "TessCoord", QShaderDescription::TessCoordBuiltin },
    { "PatchVertices", QShaderDescription::PatchVerticesBuiltin },
    { "FragCoord", QShaderDescription::FragCoordBuiltin },
    { "PointCoord", QShaderDescription::PointCoordBuiltin },
    { "FrontFacing", QShaderDescription::FrontFacingBuiltin },
    { "SampleId", QShaderDescription::SampleIdBuiltin },
    { "SamplePosition", QShaderDescription::SamplePositionBuiltin },
    { "SampleMask", QShaderDescription::SampleMaskBuiltin },
    { "FragDepth", QShaderDescription::FragDepthBuiltin },
    { "NumWorkGroups", QShaderDescription::NumWorkGroupsBuiltin },
    { "WorkgroupSize", QShaderDescription::WorkgroupSizeBuiltin },
    { "WorkgroupId", QShaderDescription::WorkgroupIdBuiltin },
    { "LocalInvocationId", QShaderDescription::LocalInvocationIdBuiltin },
    { "GlobalInvocationId", QShaderDescription::GlobalInvocationIdBuiltin },
    { "LocalInvocationIndex", QShaderDescription::LocalInvocationIndexBuiltin },
    { "VertexIndex", QShaderDescription::VertexIndexBuiltin },
    { "InstanceIndex", QShaderDescription::InstanceIndexBuiltin }
};

static QLatin1StringView builtinTypeStr(QShaderDescription::BuiltinType t)
{
    for (size_t i = 0; i < sizeof(builtinTypeTab) / sizeof(BuiltinTypeTab); ++i) {
        if (builtinTypeTab[i].v == t)
            return QLatin1StringView(builtinTypeTab[i].k);
    }
    return {};
}

static const struct TessellationModeTab {
    const char k[10];
    QShaderDescription::TessellationMode v;
} tessellationModeTab[] {
    { "unknown", QShaderDescription::UnknownTessellationMode },
    { "triangles", QShaderDescription::TrianglesTessellationMode },
    { "quad", QShaderDescription::QuadTessellationMode },
    { "isoline", QShaderDescription::IsolineTessellationMode }
};

static QLatin1StringView tessModeStr(QShaderDescription::TessellationMode mode)
{
    for (size_t i = 0; i < sizeof(tessellationModeTab) / sizeof(TessellationModeTab); ++i) {
        if (tessellationModeTab[i].v == mode)
            return QLatin1StringView(tessellationModeTab[i].k);
    }
    return {};
}

static const struct TessellationWindingOrderTab {
    const char k[8];
    QShaderDescription::TessellationWindingOrder v;
} tessellationWindingOrderTab[] {
    { "unknown", QShaderDescription::UnknownTessellationWindingOrder },
    { "cw", QShaderDescription::CwTessellationWindingOrder },
    { "ccw", QShaderDescription::CcwTessellationWindingOrder }
};

static QLatin1StringView tessWindStr(QShaderDescription::TessellationWindingOrder w)
{
    for (size_t i = 0; i < sizeof(tessellationWindingOrderTab) / sizeof(TessellationWindingOrderTab); ++i) {
        if (tessellationWindingOrderTab[i].v == w)
            return QLatin1StringView(tessellationWindingOrderTab[i].k);
    }
    return {};
}

static const struct TessellationPartitioningTab {
    const char k[24];
    QShaderDescription::TessellationPartitioning v;
} tessellationPartitioningTab[] {
    { "unknown", QShaderDescription::UnknownTessellationPartitioning },
    { "equal_spacing", QShaderDescription::EqualTessellationPartitioning },
    { "fractional_even_spacing", QShaderDescription::FractionalEvenTessellationPartitioning },
    { "fractional_odd_spacing", QShaderDescription::FractionalOddTessellationPartitioning }
};

static QLatin1StringView tessPartStr(QShaderDescription::TessellationPartitioning p)
{
    for (size_t i = 0; i < sizeof(tessellationPartitioningTab) / sizeof(TessellationPartitioningTab); ++i) {
        if (tessellationPartitioningTab[i].v == p)
            return QLatin1StringView(tessellationPartitioningTab[i].k);
    }
    return {};
}

#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug dbg, const QShaderDescription &sd)
{
    const QShaderDescriptionPrivate *d = sd.d;
    QDebugStateSaver saver(dbg);

    if (sd.isValid()) {
        dbg.nospace() << "QShaderDescription("
                      << "inVars " << d->inVars
                      << " outVars " << d->outVars
                      << " uniformBlocks " << d->uniformBlocks
                      << " pcBlocks " << d->pushConstantBlocks
                      << " storageBlocks " << d->storageBlocks
                      << " combinedSamplers " << d->combinedImageSamplers
                      << " storageImages " << d->storageImages
                      << " separateImages " << d->separateImages
                      << " separateSamplers " << d->separateSamplers
                      << " inBuiltins " << d->inBuiltins
                      << " outBuiltins " << d->outBuiltins
                      << ')';
    } else {
        dbg.nospace() << "QShaderDescription(null)";
    }

    return dbg;
}

QDebug operator<<(QDebug dbg, const QShaderDescription::InOutVariable &var)
{
    QDebugStateSaver saver(dbg);
    dbg.nospace() << "InOutVariable(" << typeStr(var.type) << ' ' << var.name;
    if (var.perPatch)
        dbg.nospace() << " per-patch";
    if (var.location >= 0)
        dbg.nospace() << " location=" << var.location;
    if (var.binding >= 0)
        dbg.nospace() << " binding=" << var.binding;
    if (var.descriptorSet >= 0)
        dbg.nospace() << " set=" << var.descriptorSet;
    if (var.imageFormat != QShaderDescription::ImageFormatUnknown)
        dbg.nospace() << " imageFormat=" << imageFormatStr(var.imageFormat);
    if (var.imageFlags)
        dbg.nospace() << " imageFlags=" << var.imageFlags;
    if (!var.arrayDims.isEmpty())
        dbg.nospace() << " array=" << var.arrayDims;
    if (!var.structMembers.isEmpty())
        dbg.nospace() << " structMembers=" << var.structMembers;
    dbg.nospace() << ')';
    return dbg;
}

QDebug operator<<(QDebug dbg, const QShaderDescription::BlockVariable &var)
{
    QDebugStateSaver saver(dbg);
    dbg.nospace() << "BlockVariable(" << typeStr(var.type) << ' ' << var.name;
    if (var.offset != -1)
        dbg.nospace() << " offset=" << var.offset;
    dbg.nospace() << " size=" << var.size;
    if (!var.arrayDims.isEmpty())
        dbg.nospace() << " array=" << var.arrayDims;
    if (var.arrayStride)
        dbg.nospace() << " arrayStride=" << var.arrayStride;
    if (var.matrixStride)
        dbg.nospace() << " matrixStride=" << var.matrixStride;
    if (var.matrixIsRowMajor)
        dbg.nospace() << " [rowmaj]";
    if (!var.structMembers.isEmpty())
        dbg.nospace() << " structMembers=" << var.structMembers;
    dbg.nospace() << ')';
    return dbg;
}

QDebug operator<<(QDebug dbg, const QShaderDescription::UniformBlock &blk)
{
    QDebugStateSaver saver(dbg);
    dbg.nospace() << "UniformBlock(" << blk.blockName << ' ' << blk.structName
                  << " size=" << blk.size;
    if (blk.binding >= 0)
        dbg.nospace() << " binding=" << blk.binding;
    if (blk.descriptorSet >= 0)
        dbg.nospace() << " set=" << blk.descriptorSet;
    dbg.nospace() << ' ' << blk.members << ')';
    return dbg;
}

QDebug operator<<(QDebug dbg, const QShaderDescription::PushConstantBlock &blk)
{
    QDebugStateSaver saver(dbg);
    dbg.nospace() << "PushConstantBlock(" << blk.name << " size=" << blk.size << ' ' << blk.members
                  << ')';
    return dbg;
}

QDebug operator<<(QDebug dbg, const QShaderDescription::StorageBlock &blk)
{
    QDebugStateSaver saver(dbg);
    dbg.nospace() << "StorageBlock(" << blk.blockName << ' ' << blk.instanceName
                  << " knownSize=" << blk.knownSize;
    if (blk.binding >= 0)
        dbg.nospace() << " binding=" << blk.binding;
    if (blk.descriptorSet >= 0)
        dbg.nospace() << " set=" << blk.descriptorSet;
    if (blk.runtimeArrayStride)
        dbg.nospace() << " runtimeArrayStride=" << blk.runtimeArrayStride;
    if (blk.qualifierFlags)
        dbg.nospace() << " qualifierFlags=" << blk.qualifierFlags;
    dbg.nospace() << ' ' << blk.members << ')';
    return dbg;
}

QDebug operator<<(QDebug dbg, const QShaderDescription::BuiltinVariable &builtin)
{
    QDebugStateSaver saver(dbg);
    dbg.nospace() << "BuiltinVariable(type=" << builtinTypeStr(builtin.type);
    dbg.nospace() << " varType=" << typeStr(builtin.varType);
    if (!builtin.arrayDims.isEmpty())
        dbg.nospace() << " array=" << builtin.arrayDims;
    dbg.nospace() << ")";
    return dbg;
}
#endif

#define JSON_KEY(key) static constexpr QLatin1StringView key ## Key() noexcept { return QLatin1StringView( #key ); }
JSON_KEY(name)
JSON_KEY(type)
JSON_KEY(location)
JSON_KEY(binding)
JSON_KEY(set)
JSON_KEY(perPatch)
JSON_KEY(imageFormat)
JSON_KEY(imageFlags)
JSON_KEY(offset)
JSON_KEY(arrayDims)
JSON_KEY(arrayStride)
JSON_KEY(matrixStride)
JSON_KEY(matrixRowMajor)
JSON_KEY(structMembers)
JSON_KEY(members)
JSON_KEY(inputs)
JSON_KEY(outputs)
JSON_KEY(uniformBlocks)
JSON_KEY(blockName)
JSON_KEY(structName)
JSON_KEY(instanceName)
JSON_KEY(size)
JSON_KEY(knownSize)
JSON_KEY(pushConstantBlocks)
JSON_KEY(storageBlocks)
JSON_KEY(combinedImageSamplers)
JSON_KEY(storageImages)
JSON_KEY(inBuiltins)
JSON_KEY(outBuiltins)
JSON_KEY(computeLocalSize)
JSON_KEY(tessellationOutputVertexCount)
JSON_KEY(tessellationMode)
JSON_KEY(tessellationWindingOrder)
JSON_KEY(tessellationPartitioning)
JSON_KEY(separateImages)
JSON_KEY(separateSamplers)
JSON_KEY(runtimeArrayStride)
JSON_KEY(qualifierFlags)
#undef JSON_KEY

static void addDeco(QJsonObject *obj, const QShaderDescription::InOutVariable &v)
{
    if (v.location >= 0)
        (*obj)[locationKey()] = v.location;
    if (v.binding >= 0)
        (*obj)[bindingKey()] = v.binding;
    if (v.descriptorSet >= 0)
        (*obj)[setKey()] = v.descriptorSet;
    if (v.perPatch)
        (*obj)[perPatchKey()] = v.perPatch;
    if (v.imageFormat != QShaderDescription::ImageFormatUnknown)
        (*obj)[imageFormatKey()] = imageFormatStr(v.imageFormat);
    if (v.imageFlags)
        (*obj)[imageFlagsKey()] = int(v.imageFlags);
    if (!v.arrayDims.isEmpty()) {
        QJsonArray dimArr;
        for (int dim : v.arrayDims)
            dimArr.append(dim);
        (*obj)[arrayDimsKey()] = dimArr;
    }
}

static void serializeDecorations(QDataStream *stream, const QShaderDescription::InOutVariable &v, int version)
{
    (*stream) << v.location;
    (*stream) << v.binding;
    (*stream) << v.descriptorSet;
    (*stream) << int(v.imageFormat);
    (*stream) << int(v.imageFlags);
    (*stream) << int(v.arrayDims.size());
    for (int dim : v.arrayDims)
        (*stream) << dim;
    if (version > QShaderPrivate::QSB_VERSION_WITHOUT_NATIVE_SHADER_INFO)
        (*stream) << quint8(v.perPatch);
}

static void serializeBuiltinVar(QDataStream *stream, const QShaderDescription::BuiltinVariable &v, int version)
{
    (*stream) << int(v.type);
    if (version > QShaderPrivate::QSB_VERSION_WITHOUT_INPUT_OUTPUT_INTERFACE_BLOCKS) {
        (*stream) << int(v.varType);
        (*stream) << int(v.arrayDims.size());
        for (int dim : v.arrayDims)
            (*stream) << dim;
    }
}

static QJsonObject blockMemberObject(const QShaderDescription::BlockVariable &v)
{
    QJsonObject obj;
    obj[nameKey()] = QString::fromUtf8(v.name);
    obj[typeKey()] = typeStr(v.type);
    if (v.offset != -1)
        obj[offsetKey()] = v.offset;
    obj[sizeKey()] = v.size;
    if (!v.arrayDims.isEmpty()) {
        QJsonArray dimArr;
        for (int dim : v.arrayDims)
            dimArr.append(dim);
        obj[arrayDimsKey()] = dimArr;
    }
    if (v.arrayStride)
        obj[arrayStrideKey()] = v.arrayStride;
    if (v.matrixStride)
        obj[matrixStrideKey()] = v.matrixStride;
    if (v.matrixIsRowMajor)
        obj[matrixRowMajorKey()] = true;
    if (!v.structMembers.isEmpty()) {
        QJsonArray arr;
        for (const QShaderDescription::BlockVariable &sv : v.structMembers)
            arr.append(blockMemberObject(sv));
        obj[structMembersKey()] = arr;
    }
    return obj;
}

static QJsonObject inOutObject(const QShaderDescription::InOutVariable &v)
{
    QJsonObject obj;
    obj[nameKey()] = QString::fromUtf8(v.name);
    obj[typeKey()] = typeStr(v.type);
    addDeco(&obj, v);
    if (!v.structMembers.isEmpty()) {
        QJsonArray arr;
        for (const QShaderDescription::BlockVariable &sv : v.structMembers)
            arr.append(blockMemberObject(sv));
        obj[structMembersKey()] = arr;
    }
    return obj;
}

static QJsonObject builtinObject(const QShaderDescription::BuiltinVariable &v)
{
    QJsonObject obj;

    obj[nameKey()] = builtinTypeStr(v.type);
    obj[typeKey()] = typeStr(v.varType);
    if (!v.arrayDims.isEmpty()) {
        QJsonArray dimArr;
        for (int dim : v.arrayDims)
            dimArr.append(dim);
        obj[arrayDimsKey()] = dimArr;
    }
    return obj;
}

static void serializeBlockMemberVar(QDataStream *stream, const QShaderDescription::BlockVariable &v)
{
    (*stream) << QString::fromUtf8(v.name);
    (*stream) << int(v.type);
    (*stream) << v.offset;
    (*stream) << v.size;
    (*stream) << int(v.arrayDims.size());
    for (int dim : v.arrayDims)
        (*stream) << dim;
    (*stream) << v.arrayStride;
    (*stream) << v.matrixStride;
    (*stream) << v.matrixIsRowMajor;
    (*stream) << int(v.structMembers.size());
    for (const QShaderDescription::BlockVariable &sv : v.structMembers)
        serializeBlockMemberVar(stream, sv);
}

static void serializeInOutVar(QDataStream *stream, const QShaderDescription::InOutVariable &v,
                              int version)
{
    (*stream) << QString::fromUtf8(v.name);
    (*stream) << int(v.type);
    serializeDecorations(stream, v, version);
    if (version > QShaderPrivate::QSB_VERSION_WITHOUT_INPUT_OUTPUT_INTERFACE_BLOCKS) {
        (*stream) << int(v.structMembers.size());
        for (const QShaderDescription::BlockVariable &sv : v.structMembers)
            serializeBlockMemberVar(stream, sv);
    }
}

QJsonDocument QShaderDescriptionPrivate::makeDoc()
{
    QJsonObject root;

    QJsonArray jinputs;
    for (const QShaderDescription::InOutVariable &v : std::as_const(inVars))
        jinputs.append(inOutObject(v));
    if (!jinputs.isEmpty())
        root[inputsKey()] = jinputs;

    QJsonArray joutputs;
    for (const QShaderDescription::InOutVariable &v : std::as_const(outVars))
        joutputs.append(inOutObject(v));
    if (!joutputs.isEmpty())
        root[outputsKey()] = joutputs;

    QJsonArray juniformBlocks;
    for (const QShaderDescription::UniformBlock &b : uniformBlocks) {
        QJsonObject juniformBlock;
        juniformBlock[blockNameKey()] = QString::fromUtf8(b.blockName);
        juniformBlock[structNameKey()] = QString::fromUtf8(b.structName);
        juniformBlock[sizeKey()] = b.size;
        if (b.binding >= 0)
            juniformBlock[bindingKey()] = b.binding;
        if (b.descriptorSet >= 0)
            juniformBlock[setKey()] = b.descriptorSet;
        QJsonArray members;
        for (const QShaderDescription::BlockVariable &v : b.members)
            members.append(blockMemberObject(v));
        juniformBlock[membersKey()] = members;
        juniformBlocks.append(juniformBlock);
    }
    if (!juniformBlocks.isEmpty())
        root[uniformBlocksKey()] = juniformBlocks;

    QJsonArray jpushConstantBlocks;
    for (const QShaderDescription::PushConstantBlock &b : pushConstantBlocks) {
        QJsonObject jpushConstantBlock;
        jpushConstantBlock[nameKey()] = QString::fromUtf8(b.name);
        jpushConstantBlock[sizeKey()] = b.size;
        QJsonArray members;
        for (const QShaderDescription::BlockVariable &v : b.members)
            members.append(blockMemberObject(v));
        jpushConstantBlock[membersKey()] = members;
        jpushConstantBlocks.append(jpushConstantBlock);
    }
    if (!jpushConstantBlocks.isEmpty())
        root[pushConstantBlocksKey()] = jpushConstantBlocks;

    QJsonArray jstorageBlocks;
    for (const QShaderDescription::StorageBlock &b : storageBlocks) {
        QJsonObject jstorageBlock;
        jstorageBlock[blockNameKey()] = QString::fromUtf8(b.blockName);
        jstorageBlock[instanceNameKey()] = QString::fromUtf8(b.instanceName);
        jstorageBlock[knownSizeKey()] = b.knownSize;
        if (b.binding >= 0)
            jstorageBlock[bindingKey()] = b.binding;
        if (b.descriptorSet >= 0)
            jstorageBlock[setKey()] = b.descriptorSet;
        if (b.runtimeArrayStride)
            jstorageBlock[runtimeArrayStrideKey()] = b.runtimeArrayStride;
        if (b.qualifierFlags)
            jstorageBlock[qualifierFlagsKey()] = int(b.qualifierFlags);
        QJsonArray members;
        for (const QShaderDescription::BlockVariable &v : b.members)
            members.append(blockMemberObject(v));
        jstorageBlock[membersKey()] = members;
        jstorageBlocks.append(jstorageBlock);
    }
    if (!jstorageBlocks.isEmpty())
        root[storageBlocksKey()] = jstorageBlocks;

    QJsonArray jcombinedSamplers;
    for (const QShaderDescription::InOutVariable &v : std::as_const(combinedImageSamplers)) {
        QJsonObject sampler;
        sampler[nameKey()] = QString::fromUtf8(v.name);
        sampler[typeKey()] = typeStr(v.type);
        addDeco(&sampler, v);
        jcombinedSamplers.append(sampler);
    }
    if (!jcombinedSamplers.isEmpty())
        root[combinedImageSamplersKey()] = jcombinedSamplers;

    QJsonArray jstorageImages;
    for (const QShaderDescription::InOutVariable &v : std::as_const(storageImages)) {
        QJsonObject image;
        image[nameKey()] = QString::fromUtf8(v.name);
        image[typeKey()] = typeStr(v.type);
        addDeco(&image, v);
        jstorageImages.append(image);
    }
    if (!jstorageImages.isEmpty())
        root[storageImagesKey()] = jstorageImages;

    QJsonArray jinBuiltins;
    for (const QShaderDescription::BuiltinVariable &v : std::as_const(inBuiltins))
        jinBuiltins.append(builtinObject(v));
    if (!jinBuiltins.isEmpty())
        root[inBuiltinsKey()] = jinBuiltins;

    QJsonArray joutBuiltins;
    for (const QShaderDescription::BuiltinVariable &v : std::as_const(outBuiltins))
        joutBuiltins.append(builtinObject(v));
    if (!joutBuiltins.isEmpty())
        root[outBuiltinsKey()] = joutBuiltins;

    if (localSize[0] || localSize[1] || localSize[2]) {
        QJsonArray jlocalSize;
        for (size_t i = 0; i < 3; ++i)
            jlocalSize.append(QJsonValue(int(localSize[i])));
        root[computeLocalSizeKey()] = jlocalSize;
    }

    if (tessOutVertCount)
        root[tessellationOutputVertexCountKey()] = int(tessOutVertCount);

    if (tessMode != QShaderDescription::UnknownTessellationMode)
        root[tessellationModeKey()] = tessModeStr(tessMode);

    if (tessWind != QShaderDescription::UnknownTessellationWindingOrder)
        root[tessellationWindingOrderKey()] = tessWindStr(tessWind);

    if (tessPart != QShaderDescription::UnknownTessellationPartitioning)
        root[tessellationPartitioningKey()] = tessPartStr(tessPart);

    QJsonArray jseparateImages;
    for (const QShaderDescription::InOutVariable &v : std::as_const(separateImages)) {
        QJsonObject image;
        image[nameKey()] = QString::fromUtf8(v.name);
        image[typeKey()] = typeStr(v.type);
        addDeco(&image, v);
        jseparateImages.append(image);
    }
    if (!jseparateImages.isEmpty())
        root[separateImagesKey()] = jseparateImages;

    QJsonArray jseparateSamplers;
    for (const QShaderDescription::InOutVariable &v : std::as_const(separateSamplers)) {
        QJsonObject sampler;
        sampler[nameKey()] = QString::fromUtf8(v.name);
        sampler[typeKey()] = typeStr(v.type);
        addDeco(&sampler, v);
        jseparateSamplers.append(sampler);
    }
    if (!jseparateSamplers.isEmpty())
        root[separateSamplersKey()] = jseparateSamplers;

    return QJsonDocument(root);
}

void QShaderDescriptionPrivate::writeToStream(QDataStream *stream, int version)
{
    (*stream) << int(inVars.size());
    for (const QShaderDescription::InOutVariable &v : std::as_const(inVars))
        serializeInOutVar(stream, v, version);

    (*stream) << int(outVars.size());
    for (const QShaderDescription::InOutVariable &v : std::as_const(outVars))
        serializeInOutVar(stream, v, version);

    (*stream) << int(uniformBlocks.size());
    for (const QShaderDescription::UniformBlock &b : uniformBlocks) {
        (*stream) << QString::fromUtf8(b.blockName);
        (*stream) << QString::fromUtf8(b.structName);
        (*stream) << b.size;
        (*stream) << b.binding;
        (*stream) << b.descriptorSet;
        (*stream) << int(b.members.size());
        for (const QShaderDescription::BlockVariable &v : b.members)
            serializeBlockMemberVar(stream, v);
    }

    (*stream) << int(pushConstantBlocks.size());
    for (const QShaderDescription::PushConstantBlock &b : pushConstantBlocks) {
        (*stream) << QString::fromUtf8(b.name);
        (*stream) << b.size;
        (*stream) << int(b.members.size());
        for (const QShaderDescription::BlockVariable &v : b.members)
            serializeBlockMemberVar(stream, v);
    }

    (*stream) << int(storageBlocks.size());
    for (const QShaderDescription::StorageBlock &b : storageBlocks) {
        (*stream) << QString::fromUtf8(b.blockName);
        (*stream) << QString::fromUtf8(b.instanceName);
        (*stream) << b.knownSize;
        (*stream) << b.binding;
        (*stream) << b.descriptorSet;
        (*stream) << int(b.members.size());
        for (const QShaderDescription::BlockVariable &v : b.members)
            serializeBlockMemberVar(stream, v);
        if (version > QShaderPrivate::QSB_VERSION_WITHOUT_EXTENDED_STORAGE_BUFFER_INFO) {
            (*stream) << b.runtimeArrayStride;
            (*stream) << b.qualifierFlags;
        }
    }

    (*stream) << int(combinedImageSamplers.size());
    for (const QShaderDescription::InOutVariable &v : std::as_const(combinedImageSamplers)) {
        (*stream) << QString::fromUtf8(v.name);
        (*stream) << int(v.type);
        serializeDecorations(stream, v, version);
    }

    (*stream) << int(storageImages.size());
    for (const QShaderDescription::InOutVariable &v : std::as_const(storageImages)) {
        (*stream) << QString::fromUtf8(v.name);
        (*stream) << int(v.type);
        serializeDecorations(stream, v, version);
    }

    for (size_t i = 0; i < 3; ++i)
        (*stream) << quint32(localSize[i]);

    (*stream) << int(separateImages.size());
    for (const QShaderDescription::InOutVariable &v : std::as_const(separateImages)) {
        (*stream) << QString::fromUtf8(v.name);
        (*stream) << int(v.type);
        serializeDecorations(stream, v, version);
    }

    (*stream) << int(separateSamplers.size());
    for (const QShaderDescription::InOutVariable &v : std::as_const(separateSamplers)) {
        (*stream) << QString::fromUtf8(v.name);
        (*stream) << int(v.type);
        serializeDecorations(stream, v, version);
    }

    if (version > QShaderPrivate::QSB_VERSION_WITHOUT_NATIVE_SHADER_INFO) {
        (*stream) << quint32(tessOutVertCount);
        (*stream) << quint32(tessMode);
        (*stream) << quint32(tessWind);
        (*stream) << quint32(tessPart);

        (*stream) << int(inBuiltins.size());
        for (const QShaderDescription::BuiltinVariable &v : std::as_const(inBuiltins))
            serializeBuiltinVar(stream, v, version);

        (*stream) << int(outBuiltins.size());
        for (const QShaderDescription::BuiltinVariable &v : std::as_const(outBuiltins))
            serializeBuiltinVar(stream, v, version);
    }
}

static void deserializeDecorations(QDataStream *stream, int version, QShaderDescription::InOutVariable *v)
{
    (*stream) >> v->location;
    (*stream) >> v->binding;
    (*stream) >> v->descriptorSet;
    int f;
    (*stream) >> f;
    v->imageFormat = QShaderDescription::ImageFormat(f);
    (*stream) >> f;
    v->imageFlags = QShaderDescription::ImageFlags(f);

    if (version > QShaderPrivate::QSB_VERSION_WITHOUT_VAR_ARRAYDIMS) {
        (*stream) >> f;
        v->arrayDims.resize(f);
        for (int i = 0; i < f; ++i)
            (*stream) >> v->arrayDims[i];
    }

    if (version > QShaderPrivate::QSB_VERSION_WITHOUT_NATIVE_SHADER_INFO) {
        quint8 b;
        (*stream) >> b;
        v->perPatch = b;
    }
}

static QShaderDescription::BuiltinVariable deserializeBuiltinVar(QDataStream *stream, int version)
{
    QShaderDescription::BuiltinVariable var;
    int t;
    (*stream) >> t;
    var.type = QShaderDescription::BuiltinType(t);
    if (version > QShaderPrivate::QSB_VERSION_WITHOUT_INPUT_OUTPUT_INTERFACE_BLOCKS) {
        (*stream) >> t;
        var.varType = QShaderDescription::VariableType(t);
        int count;
        (*stream) >> count;
        var.arrayDims.resize(count);
        for (int i = 0; i < count; ++i)
            (*stream) >> var.arrayDims[i];
    }
    return var;
}

static QShaderDescription::BlockVariable deserializeBlockMemberVar(QDataStream *stream, int version)
{
    QShaderDescription::BlockVariable var;
    QString tmp;
    (*stream) >> tmp;
    var.name = tmp.toUtf8();
    int t;
    (*stream) >> t;
    var.type = QShaderDescription::VariableType(t);
    (*stream) >> var.offset;
    (*stream) >> var.size;
    int count;
    (*stream) >> count;
    var.arrayDims.resize(count);
    for (int i = 0; i < count; ++i)
        (*stream) >> var.arrayDims[i];
    (*stream) >> var.arrayStride;
    (*stream) >> var.matrixStride;
    (*stream) >> var.matrixIsRowMajor;
    (*stream) >> count;
    var.structMembers.resize(count);
    for (int i = 0; i < count; ++i)
        var.structMembers[i] = deserializeBlockMemberVar(stream, version);
    return var;
}

static QShaderDescription::InOutVariable deserializeInOutVar(QDataStream *stream, int version)
{
    QShaderDescription::InOutVariable var;
    QString tmp;
    (*stream) >> tmp;
    var.name = tmp.toUtf8();
    int t;
    (*stream) >> t;
    var.type = QShaderDescription::VariableType(t);
    deserializeDecorations(stream, version, &var);
    if (version > QShaderPrivate::QSB_VERSION_WITHOUT_INPUT_OUTPUT_INTERFACE_BLOCKS) {
        int count;
        (*stream) >> count;
        var.structMembers.resize(count);
        for (int i = 0; i < count; ++i)
            var.structMembers[i] = deserializeBlockMemberVar(stream, version);
    }
    return var;
}

void QShaderDescriptionPrivate::loadFromStream(QDataStream *stream, int version)
{
    Q_ASSERT(ref.loadRelaxed() == 1); // must be detached

    int count;
    (*stream) >> count;
    inVars.resize(count);
    for (int i = 0; i < count; ++i)
        inVars[i] = deserializeInOutVar(stream, version);

    (*stream) >> count;
    outVars.resize(count);
    for (int i = 0; i < count; ++i)
        outVars[i] = deserializeInOutVar(stream, version);

    (*stream) >> count;
    uniformBlocks.resize(count);
    for (int i = 0; i < count; ++i) {
        QString tmp;
        (*stream) >> tmp;
        uniformBlocks[i].blockName = tmp.toUtf8();
        (*stream) >> tmp;
        uniformBlocks[i].structName = tmp.toUtf8();
        (*stream) >> uniformBlocks[i].size;
        (*stream) >> uniformBlocks[i].binding;
        (*stream) >> uniformBlocks[i].descriptorSet;
        int memberCount;
        (*stream) >> memberCount;
        uniformBlocks[i].members.resize(memberCount);
        for (int memberIdx = 0; memberIdx < memberCount; ++memberIdx)
            uniformBlocks[i].members[memberIdx] = deserializeBlockMemberVar(stream, version);
    }

    (*stream) >> count;
    pushConstantBlocks.resize(count);
    for (int i = 0; i < count; ++i) {
        QString tmp;
        (*stream) >> tmp;
        pushConstantBlocks[i].name = tmp.toUtf8();
        (*stream) >> pushConstantBlocks[i].size;
        int memberCount;
        (*stream) >> memberCount;
        pushConstantBlocks[i].members.resize(memberCount);
        for (int memberIdx = 0; memberIdx < memberCount; ++memberIdx)
            pushConstantBlocks[i].members[memberIdx] = deserializeBlockMemberVar(stream, version);
    }

    (*stream) >> count;
    storageBlocks.resize(count);
    for (int i = 0; i < count; ++i) {
        QString tmp;
        (*stream) >> tmp;
        storageBlocks[i].blockName = tmp.toUtf8();
        (*stream) >> tmp;
        storageBlocks[i].instanceName = tmp.toUtf8();
        (*stream) >> storageBlocks[i].knownSize;
        (*stream) >> storageBlocks[i].binding;
        (*stream) >> storageBlocks[i].descriptorSet;
        int memberCount;
        (*stream) >> memberCount;
        storageBlocks[i].members.resize(memberCount);
        for (int memberIdx = 0; memberIdx < memberCount; ++memberIdx)
            storageBlocks[i].members[memberIdx] = deserializeBlockMemberVar(stream, version);

        if (version > QShaderPrivate::QSB_VERSION_WITHOUT_EXTENDED_STORAGE_BUFFER_INFO) {
            (*stream) >> storageBlocks[i].runtimeArrayStride;
            (*stream) >> storageBlocks[i].qualifierFlags;
        }
    }

    (*stream) >> count;
    combinedImageSamplers.resize(count);
    for (int i = 0; i < count; ++i) {
        QString tmp;
        (*stream) >> tmp;
        combinedImageSamplers[i].name = tmp.toUtf8();
        int t;
        (*stream) >> t;
        combinedImageSamplers[i].type = QShaderDescription::VariableType(t);
        deserializeDecorations(stream, version, &combinedImageSamplers[i]);
    }

    (*stream) >> count;
    storageImages.resize(count);
    for (int i = 0; i < count; ++i) {
        QString tmp;
        (*stream) >> tmp;
        storageImages[i].name = tmp.toUtf8();
        int t;
        (*stream) >> t;
        storageImages[i].type = QShaderDescription::VariableType(t);
        deserializeDecorations(stream, version, &storageImages[i]);
    }

    for (size_t i = 0; i < 3; ++i) {
        quint32 v;
        (*stream) >> v;
        localSize[i] = v;
    }

    if (version > QShaderPrivate::QSB_VERSION_WITHOUT_SEPARATE_IMAGES_AND_SAMPLERS) {
        (*stream) >> count;
        separateImages.resize(count);
        for (int i = 0; i < count; ++i) {
            QString tmp;
            (*stream) >> tmp;
            separateImages[i].name = tmp.toUtf8();
            int t;
            (*stream) >> t;
            separateImages[i].type = QShaderDescription::VariableType(t);
            deserializeDecorations(stream, version, &separateImages[i]);
        }

        (*stream) >> count;
        separateSamplers.resize(count);
        for (int i = 0; i < count; ++i) {
            QString tmp;
            (*stream) >> tmp;
            separateSamplers[i].name = tmp.toUtf8();
            int t;
            (*stream) >> t;
            separateSamplers[i].type = QShaderDescription::VariableType(t);
            deserializeDecorations(stream, version, &separateSamplers[i]);
        }
    }

    if (version > QShaderPrivate::QSB_VERSION_WITHOUT_NATIVE_SHADER_INFO) {
        quint32 v;
        (*stream) >> v;
        tessOutVertCount = v;
        (*stream) >> v;
        tessMode = QShaderDescription::TessellationMode(v);
        (*stream) >> v;
        tessWind = QShaderDescription::TessellationWindingOrder(v);
        (*stream) >> v;
        tessPart = QShaderDescription::TessellationPartitioning(v);

        (*stream) >> count;
        inBuiltins.resize(count);
        for (int i = 0; i < count; ++i)
            inBuiltins[i] = deserializeBuiltinVar(stream, version);

        (*stream) >> count;
        outBuiltins.resize(count);
        for (int i = 0; i < count; ++i)
            outBuiltins[i] = deserializeBuiltinVar(stream, version);
    }
}

/*!
    Returns \c true if the two QShaderDescription objects \a lhs and \a rhs are
    equal.

    \relates QShaderDescription
 */
bool operator==(const QShaderDescription &lhs, const QShaderDescription &rhs) noexcept
{
    if (lhs.d == rhs.d)
        return true;

    return lhs.d->inVars == rhs.d->inVars
            && lhs.d->outVars == rhs.d->outVars
            && lhs.d->uniformBlocks == rhs.d->uniformBlocks
            && lhs.d->pushConstantBlocks == rhs.d->pushConstantBlocks
            && lhs.d->storageBlocks == rhs.d->storageBlocks
            && lhs.d->combinedImageSamplers == rhs.d->combinedImageSamplers
            && lhs.d->separateImages == rhs.d->separateImages
            && lhs.d->separateSamplers == rhs.d->separateSamplers
            && lhs.d->storageImages == rhs.d->storageImages
            && lhs.d->inBuiltins == rhs.d->inBuiltins
            && lhs.d->outBuiltins == rhs.d->outBuiltins
            && lhs.d->localSize == rhs.d->localSize
            && lhs.d->tessOutVertCount == rhs.d->tessOutVertCount
            && lhs.d->tessMode == rhs.d->tessMode
            && lhs.d->tessWind == rhs.d->tessWind
            && lhs.d->tessPart == rhs.d->tessPart;
}

/*!
    Returns \c true if the two InOutVariable objects \a lhs and \a rhs are
    equal.

    \relates QShaderDescription::InOutVariable
 */
bool operator==(const QShaderDescription::InOutVariable &lhs, const QShaderDescription::InOutVariable &rhs) noexcept
{
    return lhs.name == rhs.name
            && lhs.type == rhs.type
            && lhs.location == rhs.location
            && lhs.binding == rhs.binding
            && lhs.descriptorSet == rhs.descriptorSet
            && lhs.imageFormat == rhs.imageFormat
            && lhs.imageFlags == rhs.imageFlags
            && lhs.arrayDims == rhs.arrayDims
            && lhs.perPatch == rhs.perPatch
            && lhs.structMembers == rhs.structMembers;
}

/*!
    Returns \c true if the two BlockVariable objects \a lhs and \a rhs are
    equal.

    \relates QShaderDescription::BlockVariable
 */
bool operator==(const QShaderDescription::BlockVariable &lhs, const QShaderDescription::BlockVariable &rhs) noexcept
{
    return lhs.name == rhs.name
            && lhs.type == rhs.type
            && lhs.offset == rhs.offset
            && lhs.size == rhs.size
            && lhs.arrayDims == rhs.arrayDims
            && lhs.arrayStride == rhs.arrayStride
            && lhs.matrixStride == rhs.matrixStride
            && lhs.matrixIsRowMajor == rhs.matrixIsRowMajor
            && lhs.structMembers == rhs.structMembers;
}

/*!
    Returns \c true if the two UniformBlock objects \a lhs and \a rhs are
    equal.

    \relates QShaderDescription::UniformBlock
 */
bool operator==(const QShaderDescription::UniformBlock &lhs, const QShaderDescription::UniformBlock &rhs) noexcept
{
    return lhs.blockName == rhs.blockName
            && lhs.structName == rhs.structName
            && lhs.size == rhs.size
            && lhs.binding == rhs.binding
            && lhs.descriptorSet == rhs.descriptorSet
            && lhs.members == rhs.members;
}

/*!
    Returns \c true if the two PushConstantBlock objects \a lhs and \a rhs are
    equal.

    \relates QShaderDescription::PushConstantBlock
 */
bool operator==(const QShaderDescription::PushConstantBlock &lhs, const QShaderDescription::PushConstantBlock &rhs) noexcept
{
    return lhs.name == rhs.name
            && lhs.size == rhs.size
            && lhs.members == rhs.members;
}

/*!
    Returns \c true if the two StorageBlock objects \a lhs and \a rhs are
    equal.

    \relates QShaderDescription::StorageBlock
 */
bool operator==(const QShaderDescription::StorageBlock &lhs, const QShaderDescription::StorageBlock &rhs) noexcept
{
    return lhs.blockName == rhs.blockName
            && lhs.instanceName == rhs.instanceName
            && lhs.knownSize == rhs.knownSize
            && lhs.binding == rhs.binding
            && lhs.descriptorSet == rhs.descriptorSet
            && lhs.runtimeArrayStride == rhs.runtimeArrayStride
            && lhs.qualifierFlags == rhs.qualifierFlags
            && lhs.members == rhs.members;
}

/*!
    Returns \c true if the two BuiltinVariable objects \a lhs and \a rhs are
    equal.

    \relates QShaderDescription::BuiltinVariable
 */
bool operator==(const QShaderDescription::BuiltinVariable &lhs, const QShaderDescription::BuiltinVariable &rhs) noexcept
{
    return lhs.type == rhs.type
            && lhs.varType == rhs.varType
            && lhs.arrayDims == rhs.arrayDims;
}

QT_END_NAMESPACE
