Revision control
++ // Copyright (C) 2021 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
++ // Qt-Security score:significant reason:default
++
++
++ #include "qspirvshader_p.h"
++ #include "qspirvshaderremap_p.h"
++ #include <private/qshaderdescription_p.h>
++ #include <private/qshader_p.h>
++
++ #include <spirv_cross_c.h>
++
++ QT_BEGIN_NAMESPACE
++
++ struct QSpirvShaderPrivate
++ {
++ ~QSpirvShaderPrivate();
++
++ void createCompiler(spvc_backend backend);
++ void reflect();
++
++ QShaderDescription::InOutVariable inOutVar(const spvc_reflected_resource &r);
++ QShaderDescription::BlockVariable blockVar(spvc_type_id typeId, uint32_t memberIdx);
++
++ QShader::Stage stage;
++ QByteArray ir;
++ QShaderDescription shaderDescription;
++
++ spvc_context ctx = nullptr;
++ spvc_compiler glslGen = nullptr;
++ spvc_compiler hlslGen = nullptr;
++ spvc_compiler mslGen = nullptr;
++
++ QString spirvCrossErrorMsg;
++ };
++
++ QSpirvShaderPrivate::~QSpirvShaderPrivate()
++ {
++ spvc_context_destroy(ctx);
++ }
++
++ void QSpirvShaderPrivate::createCompiler(spvc_backend backend)
++ {
++ if (!ctx) {
++ if (spvc_context_create(&ctx) != SPVC_SUCCESS) {
++ qWarning("Failed to create SPIRV-Cross context");
++ return;
++ }
++ }
++
++ const SpvId *spirv = reinterpret_cast<const SpvId *>(ir.constData());
++ size_t wordCount = size_t(ir.size()) / sizeof(SpvId);
++ spvc_parsed_ir parsedIr;
++ if (spvc_context_parse_spirv(ctx, spirv, wordCount, &parsedIr) != SPVC_SUCCESS) {
++ qWarning("Failed to parse SPIR-V: %s", spvc_context_get_last_error_string(ctx));
++ return;
++ }
++
++ spvc_compiler *outCompiler = nullptr;
++ switch (backend) {
++ case SPVC_BACKEND_GLSL:
++ outCompiler = &glslGen;
++ break;
++ case SPVC_BACKEND_HLSL:
++ outCompiler = &hlslGen;
++ break;
++ case SPVC_BACKEND_MSL:
++ outCompiler = &mslGen;
++ break;
++ default:
++ return;
++ }
++
++ if (spvc_context_create_compiler(ctx, backend, parsedIr,
++ SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, outCompiler) != SPVC_SUCCESS)
++ {
++ qWarning("Failed to create SPIRV-Cross compiler: %s", spvc_context_get_last_error_string(ctx));
++ return;
++ }
++ }
++
++ static QShaderDescription::VariableType matVarType(const spvc_type &t, QShaderDescription::VariableType compType)
++ {
++ const unsigned vecsize = spvc_type_get_vector_size(t);
++ switch (spvc_type_get_columns(t)) {
++ case 2:
++ return QShaderDescription::VariableType(compType + 4 + (vecsize == 3 ? 1 : vecsize == 4 ? 2 : 0));
++ case 3:
++ return QShaderDescription::VariableType(compType + 7 + (vecsize == 2 ? 1 : vecsize == 4 ? 2 : 0));
++ case 4:
++ return QShaderDescription::VariableType(compType + 10 + (vecsize == 2 ? 1 : vecsize == 3 ? 2 : 0));
++ default:
++ return QShaderDescription::Unknown;
++ }
++ }
++
++ static QShaderDescription::VariableType vecVarType(const spvc_type &t, QShaderDescription::VariableType compType)
++ {
++ switch (spvc_type_get_vector_size(t)) {
++ case 1:
++ return compType;
++ case 2:
++ return QShaderDescription::VariableType(compType + 1);
++ case 3:
++ return QShaderDescription::VariableType(compType + 2);
++ case 4:
++ return QShaderDescription::VariableType(compType + 3);
++ default:
++ return QShaderDescription::Unknown;
++ }
++ }
++
++ static QShaderDescription::VariableType sampledImageVarType(const spvc_type &t)
++ {
++ switch (spvc_type_get_image_dimension(t)) {
++ case SpvDim1D:
++ return spvc_type_get_image_arrayed(t) ? QShaderDescription::Sampler1DArray
++ : QShaderDescription::Sampler1D;
++ case SpvDim2D:
++ return spvc_type_get_image_arrayed(t)
++ ? (spvc_type_get_image_multisampled(t) ? QShaderDescription::Sampler2DMSArray
++ : QShaderDescription::Sampler2DArray)
++ : (spvc_type_get_image_multisampled(t) ? QShaderDescription::Sampler2DMS
++ : QShaderDescription::Sampler2D);
++ case SpvDim3D:
++ return spvc_type_get_image_arrayed(t) ? QShaderDescription::Sampler3DArray
++ : QShaderDescription::Sampler3D;
++ case SpvDimCube:
++ return spvc_type_get_image_arrayed(t) ? QShaderDescription::SamplerCubeArray
++ : QShaderDescription::SamplerCube;
++ case SpvDimRect:
++ return QShaderDescription::SamplerRect;
++ case SpvDimBuffer:
++ return QShaderDescription::SamplerBuffer;
++ default:
++ return QShaderDescription::Unknown;
++ }
++ }
++
++ static QShaderDescription::VariableType imageVarType(const spvc_type &t)
++ {
++ switch (spvc_type_get_image_dimension(t)) {
++ case SpvDim1D:
++ return spvc_type_get_image_arrayed(t) ? QShaderDescription::Image1DArray
++ : QShaderDescription::Image1D;
++ case SpvDim2D:
++ return spvc_type_get_image_arrayed(t)
++ ? (spvc_type_get_image_multisampled(t) ? QShaderDescription::Image2DMSArray
++ : QShaderDescription::Image2DArray)
++ : (spvc_type_get_image_multisampled(t) ? QShaderDescription::Image2DMS
++ : QShaderDescription::Image2D);
++ case SpvDim3D:
++ return spvc_type_get_image_arrayed(t) ? QShaderDescription::Image3DArray
++ : QShaderDescription::Image3D;
++ case SpvDimCube:
++ return spvc_type_get_image_arrayed(t) ? QShaderDescription::ImageCubeArray
++ : QShaderDescription::ImageCube;
++ case SpvDimRect:
++ return QShaderDescription::ImageRect;
++ case SpvDimBuffer:
++ return QShaderDescription::ImageBuffer;
++ default:
++ return QShaderDescription::Unknown;
++ }
++ }
++
++ static QShaderDescription::VariableType varType(const spvc_type &t)
++ {
++ QShaderDescription::VariableType vt = QShaderDescription::Unknown;
++ const spvc_basetype basetype = spvc_type_get_basetype(t);
++ switch (basetype) {
++ case SPVC_BASETYPE_FP32:
++ vt = spvc_type_get_columns(t) > 1 ? matVarType(t, QShaderDescription::Float)
++ : vecVarType(t, QShaderDescription::Float);
++ break;
++ case SPVC_BASETYPE_FP64:
++ vt = spvc_type_get_columns(t) > 1 ? matVarType(t, QShaderDescription::Double)
++ : vecVarType(t, QShaderDescription::Double);
++ break;
++ case SPVC_BASETYPE_UINT32:
++ vt = vecVarType(t, QShaderDescription::Uint);
++ break;
++ case SPVC_BASETYPE_INT32:
++ vt = vecVarType(t, QShaderDescription::Int);
++ break;
++ case SPVC_BASETYPE_BOOLEAN:
++ vt = vecVarType(t, QShaderDescription::Uint);
++ break;
++ case SPVC_BASETYPE_SAMPLED_IMAGE:
++ vt = sampledImageVarType(t);
++ break;
++ case SPVC_BASETYPE_IMAGE:
++ vt = imageVarType(t);
++ break;
++ case SPVC_BASETYPE_SAMPLER:
++ vt = QShaderDescription::Sampler;
++ break;
++ case SPVC_BASETYPE_STRUCT:
++ vt = QShaderDescription::Struct;
++ break;
++ default:
++ // can encounter types we do not (yet) handle, return Unknown for those
++ qWarning("Unsupported base type %d", basetype);
++ break;
++ }
++ return vt;
++ }
++
++ QShaderDescription::InOutVariable QSpirvShaderPrivate::inOutVar(const spvc_reflected_resource &r)
++ {
++ QShaderDescription::InOutVariable v;
++ v.name = r.name;
++
++ spvc_type baseTypeHandle = spvc_compiler_get_type_handle(glslGen, r.base_type_id);
++ v.type = varType(baseTypeHandle);
++
++ spvc_type typeHandle = spvc_compiler_get_type_handle(glslGen, r.type_id);
++ for (unsigned i = 0, dimCount = spvc_type_get_num_array_dimensions(typeHandle); i < dimCount; ++i)
++ v.arrayDims.append(int(spvc_type_get_array_dimension(typeHandle, i)));
++
++ if (spvc_compiler_has_decoration(glslGen, r.id, SpvDecorationLocation))
++ v.location = spvc_compiler_get_decoration(glslGen, r.id, SpvDecorationLocation);
++
++ if (spvc_compiler_has_decoration(glslGen, r.id, SpvDecorationBinding))
++ v.binding = spvc_compiler_get_decoration(glslGen, r.id, SpvDecorationBinding);
++
++ if (spvc_compiler_has_decoration(glslGen, r.id, SpvDecorationDescriptorSet))
++ v.descriptorSet = spvc_compiler_get_decoration(glslGen, r.id, SpvDecorationDescriptorSet);
++
++ if (spvc_compiler_has_decoration(glslGen, r.id, SpvDecorationPatch))
++ v.perPatch = spvc_compiler_get_decoration(glslGen, r.id, SpvDecorationPatch);
++
++ if (spvc_type_get_basetype(baseTypeHandle) == SPVC_BASETYPE_IMAGE) {
++ v.imageFormat = QShaderDescription::ImageFormat(spvc_type_get_image_storage_format(baseTypeHandle));
++
++ v.imageFlags.setFlag(QShaderDescription::ImageFlag::WriteOnlyImage,
++ spvc_compiler_has_decoration(glslGen, r.id, SpvDecorationNonReadable));
++
++ v.imageFlags.setFlag(QShaderDescription::ImageFlag::ReadOnlyImage,
++ spvc_compiler_has_decoration(glslGen, r.id, SpvDecorationNonWritable));
++ }
++
++ if (v.type == QShaderDescription::Struct) {
++ const unsigned count = spvc_type_get_num_member_types(baseTypeHandle);
++ const spvc_type_id id = spvc_type_get_base_type_id(baseTypeHandle);
++
++ for (unsigned idx = 0; idx < count; ++idx) {
++ v.structMembers.append(blockVar(id, idx));
++ v.perPatch |= bool(spvc_compiler_has_member_decoration(glslGen, id, idx, SpvDecorationPatch));
++ }
++ }
++
++ return v;
++ }
++
++ QShaderDescription::BlockVariable QSpirvShaderPrivate::blockVar(spvc_type_id typeId, uint32_t memberIdx)
++ {
++ QShaderDescription::BlockVariable v;
++ v.name = spvc_compiler_get_member_name(glslGen, typeId, memberIdx);
++
++ spvc_type t = spvc_compiler_get_type_handle(glslGen, typeId);
++ spvc_type_id memberTypeId = spvc_type_get_member_type(t, memberIdx);
++ spvc_type memberType = spvc_compiler_get_type_handle(glslGen, memberTypeId);
++ v.type = varType(memberType);
++ v.offset = -1;
++
++ unsigned offset = 0;
++ if (spvc_compiler_type_struct_member_offset(glslGen, t, memberIdx, &offset) == SPVC_SUCCESS)
++ v.offset = int(offset);
++
++ size_t size = 0;
++ if (spvc_compiler_get_declared_struct_member_size(glslGen, t, memberIdx, &size) == SPVC_SUCCESS)
++ v.size = int(size);
++
++ for (unsigned i = 0, dimCount = spvc_type_get_num_array_dimensions(memberType); i < dimCount; ++i)
++ v.arrayDims.append(int(spvc_type_get_array_dimension(memberType, i)));
++
++ if (spvc_compiler_has_member_decoration(glslGen, typeId, memberIdx, SpvDecorationArrayStride)) {
++ unsigned stride = 0;
++ if (spvc_compiler_type_struct_member_array_stride(glslGen, t, memberIdx, &stride) == SPVC_SUCCESS)
++ v.arrayStride = int(stride);
++ }
++
++ if (spvc_compiler_has_member_decoration(glslGen, typeId, memberIdx, SpvDecorationMatrixStride)) {
++ unsigned stride = 0;
++ if (spvc_compiler_type_struct_member_matrix_stride(glslGen, t, memberIdx, &stride) == SPVC_SUCCESS)
++ v.matrixStride = int(stride);
++ }
++
++ if (spvc_compiler_has_member_decoration(glslGen, typeId, memberIdx, SpvDecorationRowMajor))
++ v.matrixIsRowMajor = true;
++
++ if (v.type == QShaderDescription::Struct) {
++ unsigned count = spvc_type_get_num_member_types(memberType);
++ for (unsigned idx = 0; idx < count; ++idx)
++ v.structMembers.append(blockVar(spvc_type_get_base_type_id(memberType), idx));
++ }
++
++ return v;
++ }
++
++ void QSpirvShaderPrivate::reflect()
++ {
++ if (!glslGen)
++ return;
++
++ shaderDescription = QShaderDescription();
++ QShaderDescriptionPrivate *dd = QShaderDescriptionPrivate::get(&shaderDescription);
++
++ dd->localSize[0] = spvc_compiler_get_execution_mode_argument_by_index(glslGen, SpvExecutionModeLocalSize, 0);
++ dd->localSize[1] = spvc_compiler_get_execution_mode_argument_by_index(glslGen, SpvExecutionModeLocalSize, 1);
++ dd->localSize[2] = spvc_compiler_get_execution_mode_argument_by_index(glslGen, SpvExecutionModeLocalSize, 2);
++
++ dd->tessOutVertCount = spvc_compiler_get_execution_mode_argument(glslGen, SpvExecutionModeOutputVertices);
++ dd->tessMode = QShaderDescription::UnknownTessellationMode;
++ dd->tessWind = QShaderDescription::UnknownTessellationWindingOrder;
++ dd->tessPart = QShaderDescription::UnknownTessellationPartitioning;
++
++ const SpvExecutionMode *execModes = nullptr;
++ size_t execModeCount = 0;
++ if (spvc_compiler_get_execution_modes(glslGen, &execModes, &execModeCount) != SPVC_SUCCESS) {
++ qWarning("Failed to get shader execution modes: %s", spvc_context_get_last_error_string(ctx));
++ return;
++ }
++ for (size_t i = 0; i < execModeCount; ++i) {
++ switch (execModes[i]) {
++ case SpvExecutionModeTriangles:
++ dd->tessMode = QShaderDescription::TrianglesTessellationMode;
++ break;
++ case SpvExecutionModeQuads:
++ dd->tessMode = QShaderDescription::QuadTessellationMode;
++ break;
++ case SpvExecutionModeIsolines:
++ qWarning("Isoline execution mode used for tessellation,"
++ " this is not portable and may not work as expected.");
++ dd->tessMode = QShaderDescription::IsolineTessellationMode;
++ break;
++ case SpvExecutionModeSpacingEqual:
++ dd->tessPart = QShaderDescription::EqualTessellationPartitioning;
++ break;
++ case SpvExecutionModeSpacingFractionalEven:
++ dd->tessPart = QShaderDescription::FractionalEvenTessellationPartitioning;
++ break;
++ case SpvExecutionModeSpacingFractionalOdd:
++ dd->tessPart = QShaderDescription::FractionalOddTessellationPartitioning;
++ break;
++ case SpvExecutionModeVertexOrderCw:
++ dd->tessWind = QShaderDescription::CwTessellationWindingOrder;
++ break;
++ case SpvExecutionModeVertexOrderCcw :
++ dd->tessWind = QShaderDescription::CcwTessellationWindingOrder;
++ break;
++ default:
++ break;
++ }
++ }
++
++ // For builtin inputs/outputs (think of things like gl_Position (or
++ // gl_out[].gl_Position), gl_InvocationID, gl_TessLevelOuter, etc.) we only
++ // want the ones that are active (really read or written).
++ spvc_compiler_update_active_builtins(glslGen);
++
++ spvc_resources resources;
++ if (spvc_compiler_create_shader_resources(glslGen, &resources) != SPVC_SUCCESS) {
++ qWarning("Failed to get shader resources: %s", spvc_context_get_last_error_string(ctx));
++ return;
++ }
++
++ const spvc_reflected_resource *resourceList = nullptr;
++ const spvc_reflected_builtin_resource *builtinResourceList = nullptr;
++ size_t resourceListCount = 0;
++
++ if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_STAGE_INPUT,
++ &resourceList, &resourceListCount) == SPVC_SUCCESS)
++ {
++ for (size_t i = 0; i < resourceListCount; ++i) {
++ const QShaderDescription::InOutVariable v = inOutVar(resourceList[i]);
++ if (v.type != QShaderDescription::Unknown)
++ dd->inVars.append(v);
++ }
++ }
++
++ if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_STAGE_OUTPUT,
++ &resourceList, &resourceListCount) == SPVC_SUCCESS)
++ {
++ for (size_t i = 0; i < resourceListCount; ++i) {
++ const QShaderDescription::InOutVariable v = inOutVar(resourceList[i]);
++ if (v.type != QShaderDescription::Unknown)
++ dd->outVars.append(v);
++ }
++ }
++
++ if (spvc_resources_get_builtin_resource_list_for_type(resources, SPVC_BUILTIN_RESOURCE_TYPE_STAGE_INPUT,
++ &builtinResourceList, &resourceListCount) == SPVC_SUCCESS)
++ {
++ for (size_t i = 0; i < resourceListCount; ++i) {
++ if (spvc_compiler_has_active_builtin(glslGen, builtinResourceList[i].builtin, SpvStorageClassInput)) {
++ QShaderDescription::BuiltinVariable v;
++ v.type = QShaderDescription::BuiltinType(builtinResourceList[i].builtin);
++
++ spvc_type type = spvc_compiler_get_type_handle(
++ glslGen, builtinResourceList[i].value_type_id);
++ v.varType = varType(type);
++
++ for (unsigned i = 0, dimCount = spvc_type_get_num_array_dimensions(type);
++ i < dimCount; ++i)
++ v.arrayDims.append(int(spvc_type_get_array_dimension(type, i)));
++
++ dd->inBuiltins.append(v);
++ }
++ }
++ std::sort(dd->inBuiltins.begin(), dd->inBuiltins.end(),
++ [](const QShaderDescription::BuiltinVariable &a, const QShaderDescription::BuiltinVariable &b)
++ {
++ return a.type < b.type;
++ });
++ }
++
++ if (spvc_resources_get_builtin_resource_list_for_type(resources, SPVC_BUILTIN_RESOURCE_TYPE_STAGE_OUTPUT,
++ &builtinResourceList, &resourceListCount) == SPVC_SUCCESS)
++ {
++ for (size_t i = 0; i < resourceListCount; ++i) {
++ if (spvc_compiler_has_active_builtin(glslGen, builtinResourceList[i].builtin, SpvStorageClassOutput)) {
++ QShaderDescription::BuiltinVariable v;
++ v.type = QShaderDescription::BuiltinType(builtinResourceList[i].builtin);
++
++ spvc_type type = spvc_compiler_get_type_handle(
++ glslGen, builtinResourceList[i].value_type_id);
++ v.varType = varType(type);
++
++ for (unsigned i = 0, dimCount = spvc_type_get_num_array_dimensions(type);
++ i < dimCount; ++i)
++ v.arrayDims.append(int(spvc_type_get_array_dimension(type, i)));
++
++ dd->outBuiltins.append(v);
++ }
++ }
++ std::sort(dd->outBuiltins.begin(), dd->outBuiltins.end(),
++ [](const QShaderDescription::BuiltinVariable &a, const QShaderDescription::BuiltinVariable &b)
++ {
++ return a.type < b.type;
++ });
++ }
++
++ // uniform blocks map to either a uniform buffer or a plain struct
++ if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_UNIFORM_BUFFER,
++ &resourceList, &resourceListCount) == SPVC_SUCCESS)
++ {
++ for (size_t i = 0; i < resourceListCount; ++i) {
++ const spvc_reflected_resource &r(resourceList[i]);
++ spvc_type t = spvc_compiler_get_type_handle(glslGen, r.base_type_id);
++ QShaderDescription::UniformBlock block;
++ block.blockName = r.name;
++
++ // the simple case:
++ // layout(...) uniform blk { T v; } inst;
++ // gives us blockName "blk" and structName "inst" because
++ // in GLSL without uniform blocks this ends up being
++ // struct blk { T v; }; uniform blk inst;
++ // or, where real UBs are used, for instance Metal:
++ // struct blk { T v; }; constant blk& inst [[buffer(N)]]
++ block.structName = spvc_compiler_get_name(glslGen, r.id);
++
++ // the annoying case:
++ // layout(...) uniform blk { T v; };
++ // here structName is empty and the generated code (when no uniform
++ // blocks) uses _ID as a fallback name:
++ // struct blk { T v; }; uniform blk _ID;
++ // or, with real uniform buffers, f.ex. Metal:
++ // struct blk { T v; }; constant blk& _ID [[buffer(N)]]
++ // Let's make sure the fallback name is filled in correctly.
++ if (block.structName.isEmpty()) {
++ // The catch (matters only when uniform blocks are not used in
++ // the output) is that ID is per-shader and so may differ
++ // between shaders, meaning that having the same uniform block
++ // in a vertex and fragment shader will lead to each stage
++ // having its own set of uniforms corresponding to the ub
++ // members (ofc, inactive uniforms may get optimized out by the
++ // compiler, so the duplication then is only there for members
++ // used in both stages). Not much we can do about that here,
++ // though. The GL backend of QRhi can deal with this.
++ block.structName = QByteArrayLiteral("_") + QByteArray::number(r.id);
++ }
++
++ size_t size = 0;
++ spvc_compiler_get_declared_struct_size(glslGen, t, &size);
++ block.size = int(size);
++ if (spvc_compiler_has_decoration(glslGen, r.id, SpvDecorationBinding))
++ block.binding = int(spvc_compiler_get_decoration(glslGen, r.id, SpvDecorationBinding));
++ if (spvc_compiler_has_decoration(glslGen, r.id, SpvDecorationDescriptorSet))
++ block.descriptorSet = int(spvc_compiler_get_decoration(glslGen, r.id, SpvDecorationDescriptorSet));
++
++ unsigned count = spvc_type_get_num_member_types(t);
++ for (unsigned idx = 0; idx < count; ++idx) {
++ const QShaderDescription::BlockVariable v = blockVar(r.base_type_id, idx);
++ if (v.type != QShaderDescription::Unknown)
++ block.members.append(v);
++ }
++
++ dd->uniformBlocks.append(block);
++ }
++ }
++
++ // push constant blocks map to a plain GLSL struct regardless of version
++ if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_PUSH_CONSTANT,
++ &resourceList, &resourceListCount) == SPVC_SUCCESS)
++ {
++ for (size_t i = 0; i < resourceListCount; ++i) {
++ const spvc_reflected_resource &r(resourceList[i]);
++ spvc_type t = spvc_compiler_get_type_handle(glslGen, r.base_type_id);
++ QShaderDescription::PushConstantBlock block;
++ block.name = spvc_compiler_get_name(glslGen, r.id);
++ if (block.name.isEmpty()) {
++ // The block was declared without an instance name. Match the
++ // fallback SPIRV-Cross generates, so that the name is still
++ // usable for looking up the members of the plain uniform
++ // struct in the GLSL output. (same as for uniform blocks)
++ block.name = QByteArrayLiteral("_") + QByteArray::number(r.id);
++ }
++ size_t size = 0;
++ spvc_compiler_get_declared_struct_size(glslGen, t, &size);
++ block.size = int(size);
++ unsigned count = spvc_type_get_num_member_types(t);
++ for (unsigned idx = 0; idx < count; ++idx) {
++ const QShaderDescription::BlockVariable v = blockVar(r.base_type_id, idx);
++ if (v.type != QShaderDescription::Unknown)
++ block.members.append(v);
++ }
++ dd->pushConstantBlocks.append(block);
++ }
++ }
++
++ if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_STORAGE_BUFFER,
++ &resourceList, &resourceListCount) == SPVC_SUCCESS)
++ {
++ for (size_t i = 0; i < resourceListCount; ++i) {
++ const spvc_reflected_resource &r(resourceList[i]);
++ spvc_type t = spvc_compiler_get_type_handle(glslGen, r.base_type_id);
++ QShaderDescription::StorageBlock block;
++ block.blockName = r.name;
++ block.instanceName = spvc_compiler_get_name(glslGen, r.id);
++ size_t size = 0;
++ spvc_compiler_get_declared_struct_size(glslGen, t, &size);
++ block.knownSize = int(size);
++ if (spvc_compiler_has_decoration(glslGen, r.id, SpvDecorationBinding))
++ block.binding = int(spvc_compiler_get_decoration(glslGen, r.id, SpvDecorationBinding));
++ if (spvc_compiler_has_decoration(glslGen, r.id, SpvDecorationDescriptorSet))
++ block.descriptorSet = int(spvc_compiler_get_decoration(glslGen, r.id, SpvDecorationDescriptorSet));
++ size_t offset0 = 0;
++ spvc_compiler_get_declared_struct_size_runtime_array(glslGen, t, 0, &offset0);
++ size_t offset1 = 0;
++ spvc_compiler_get_declared_struct_size_runtime_array(glslGen, t, 1, &offset1);
++ block.runtimeArrayStride = int(offset1 - offset0);
++ const SpvDecoration *decorations;
++ spvc_compiler_get_buffer_block_decorations(glslGen, r.id, &decorations, &size);
++ for (uint i = 0; i < size; ++i) {
++ switch (decorations[i]) {
++ case SpvDecorationCoherent:
++ block.qualifierFlags.setFlag(QShaderDescription::QualifierCoherent);
++ break;
++ case SpvDecorationVolatile:
++ block.qualifierFlags.setFlag(QShaderDescription::QualifierVolatile);
++ break;
++ case SpvDecorationRestrict:
++ block.qualifierFlags.setFlag(QShaderDescription::QualifierRestrict);
++ break;
++ case SpvDecorationNonWritable:
++ block.qualifierFlags.setFlag(QShaderDescription::QualifierReadOnly);
++ break;
++ case SpvDecorationNonReadable:
++ block.qualifierFlags.setFlag(QShaderDescription::QualifierWriteOnly);
++ break;
++ default:
++ break;
++ }
++ }
++ unsigned count = spvc_type_get_num_member_types(t);
++ for (unsigned idx = 0; idx < count; ++idx) {
++ const QShaderDescription::BlockVariable v = blockVar(r.base_type_id, idx);
++ if (v.type != QShaderDescription::Unknown)
++ block.members.append(v);
++ }
++ dd->storageBlocks.append(block);
++ }
++ }
++
++ if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_SAMPLED_IMAGE,
++ &resourceList, &resourceListCount) == SPVC_SUCCESS)
++ {
++ for (size_t i = 0; i < resourceListCount; ++i) {
++ const spvc_reflected_resource &r(resourceList[i]);
++ const QShaderDescription::InOutVariable v = inOutVar(r);
++ if (v.type != QShaderDescription::Unknown)
++ dd->combinedImageSamplers.append(v);
++ }
++ }
++
++ if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_SEPARATE_IMAGE,
++ &resourceList, &resourceListCount) == SPVC_SUCCESS)
++ {
++ for (size_t i = 0; i < resourceListCount; ++i) {
++ const spvc_reflected_resource &r(resourceList[i]);
++ const QShaderDescription::InOutVariable v = inOutVar(r);
++ if (v.type != QShaderDescription::Unknown)
++ dd->separateImages.append(v);
++ }
++ }
++
++ if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_SEPARATE_SAMPLERS,
++ &resourceList, &resourceListCount) == SPVC_SUCCESS)
++ {
++ for (size_t i = 0; i < resourceListCount; ++i) {
++ const spvc_reflected_resource &r(resourceList[i]);
++ const QShaderDescription::InOutVariable v = inOutVar(r);
++ if (v.type != QShaderDescription::Unknown)
++ dd->separateSamplers.append(v);
++ }
++ }
++
++ if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_STORAGE_IMAGE,
++ &resourceList, &resourceListCount) == SPVC_SUCCESS)
++ {
++ for (size_t i = 0; i < resourceListCount; ++i) {
++ const spvc_reflected_resource &r(resourceList[i]);
++ const QShaderDescription::InOutVariable v = inOutVar(r);
++ if (v.type != QShaderDescription::Unknown)
++ dd->storageImages.append(v);
++ }
++ }
++ }
++
++ QSpirvShader::QSpirvShader()
++ : d(new QSpirvShaderPrivate)
++ {
++ }
++
++ QSpirvShader::~QSpirvShader()
++ {
++ delete d;
++ }
++
++ void QSpirvShader::setSpirvBinary(const QByteArray &spirv, QShader::Stage stage)
++ {
++ d->stage = stage;
++ d->ir = spirv;
++ d->createCompiler(SPVC_BACKEND_GLSL);
++ d->reflect();
++ }
++
++ QShaderDescription QSpirvShader::shaderDescription() const
++ {
++ return d->shaderDescription;
++ }
++
++ QByteArray QSpirvShader::spirvBinary() const
++ {
++ return d->ir;
++ }
++
++ QByteArray QSpirvShader::remappedSpirvBinary(RemapFlags flags, QString *errorMessage) const
++ {
++ QSpirvShaderRemapper remapper;
++ QByteArray result = remapper.remap(d->ir, flags);
++ if (errorMessage)
++ *errorMessage = remapper.errorMessage();
++ return result;
++ }
++
++ QByteArray QSpirvShader::translateToGLSL(int version,
++ GlslFlags flags,
++ QShader::Stage stage,
++ const MultiViewInfo &multiViewInfo,
++ QVector<SeparateToCombinedImageSamplerMapping> *separateToCombinedImageSamplerMappings) const
++ {
++ d->spirvCrossErrorMsg.clear();
++
++ d->createCompiler(SPVC_BACKEND_GLSL);
++ if (!d->glslGen)
++ return QByteArray();
++
++ spvc_compiler_options options = nullptr;
++ if (spvc_compiler_create_compiler_options(d->glslGen, &options) != SPVC_SUCCESS)
++ return QByteArray();
++
++ spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_GLSL_VERSION,
++ version);
++ spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_ES,
++ flags.testFlag(GlslFlag::GlslEs));
++ spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_FIXUP_DEPTH_CONVENTION,
++ flags.testFlag(GlslFlag::FixClipSpace));
++ spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_ES_DEFAULT_FLOAT_PRECISION_HIGHP,
++ !flags.testFlag(GlslFlag::EsFragDefaultFloatPrecisionMedium));
++ // The gl backend of QRhi is not prepared for UBOs atm. Have a uniform (heh)
++ // behavior regardless of the GLSL version.
++ spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_EMIT_UNIFORM_BUFFER_AS_PLAIN_UNIFORMS,
++ true);
++ // Do not emit binding qualifiers for samplers (and for uniform blocks, but
++ // those we just disabled above).
++ spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_ENABLE_420PACK_EXTENSION,
++ false);
++
++ if (stage == QShader::VertexStage && multiViewInfo.viewCount > 1) {
++ spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_GLSL_OVR_MULTIVIEW_VIEW_COUNT,
++ uint(multiViewInfo.viewCount));
++ }
++
++ spvc_compiler_install_compiler_options(d->glslGen, options);
++
++ // Let's say the shader has these separate imagers and samplers:
++ // layout(binding = 2) uniform texture2D sepTex;
++ // layout(binding = 3) uniform sampler sepSampler;
++ // layout(binding = 4) uniform sampler sepSampler2;
++ // where sepTex is used with both samplers in the shader.
++ // For OpenGL this is mapped to "combined image samplers", i.e. the
++ // traditional sampler2Ds with arbitrary names:
++ // uniform sampler2D _39;
++ // uniform sampler2D _41;
++ // This mapping (2+3 -> "_39", 2+4 -> "_41") needs to be exposed to the caller.
++ if (spvc_compiler_build_combined_image_samplers(d->glslGen) != SPVC_SUCCESS) {
++ d->spirvCrossErrorMsg = QString::fromUtf8(spvc_context_get_last_error_string(d->ctx));
++ return QByteArray();
++ }
++ if (separateToCombinedImageSamplerMappings) {
++ const spvc_combined_image_sampler *combinedImageSamplerMappings = nullptr;
++ size_t numCombinedImageSamplerMappings = 0;
++ if (spvc_compiler_get_combined_image_samplers(d->glslGen,
++ &combinedImageSamplerMappings,
++ &numCombinedImageSamplerMappings) == SPVC_SUCCESS)
++ {
++ for (size_t i = 0; i < numCombinedImageSamplerMappings; ++i) {
++ const spvc_combined_image_sampler *mapping = &combinedImageSamplerMappings[i];
++ QByteArray combinedName = spvc_compiler_get_name(d->glslGen, mapping->combined_id);
++ if (combinedName.isEmpty())
++ combinedName = QByteArrayLiteral("_") + QByteArray::number(mapping->combined_id);
++ QByteArray textureName = spvc_compiler_get_name(d->glslGen, mapping->image_id);
++ QByteArray samplerName = spvc_compiler_get_name(d->glslGen, mapping->sampler_id);
++ separateToCombinedImageSamplerMappings->append({ textureName, samplerName, combinedName });
++ }
++ }
++ }
++
++ const char *result = nullptr;
++ if (spvc_compiler_compile(d->glslGen, &result) != SPVC_SUCCESS) {
++ d->spirvCrossErrorMsg = QString::fromUtf8(spvc_context_get_last_error_string(d->ctx));
++ return QByteArray();
++ }
++
++ // We used to fix up the result to complement GL_ARB_shading_language_420pack
++ // with GL_ARB_separate_shader_objects to make Mesa happy, but the 420pack
++ // is never relied on now so no need to do anything here.
++ return result;
++ }
++
++ QByteArray QSpirvShader::translateToHLSL(int version, QShader::NativeResourceBindingMap *nativeBindings,
++ QShader::NativeShaderInfo *shaderInfo) const
++ {
++ d->spirvCrossErrorMsg.clear();
++
++ d->createCompiler(SPVC_BACKEND_HLSL);
++ if (!d->hlslGen)
++ return QByteArray();
++
++ spvc_compiler_options options = nullptr;
++ if (spvc_compiler_create_compiler_options(d->hlslGen, &options) != SPVC_SUCCESS)
++ return QByteArray();
++ spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_HLSL_SHADER_MODEL,
++ version);
++ spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_HLSL_POINT_SIZE_COMPAT,
++ true);
++ spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_HLSL_POINT_COORD_COMPAT,
++ true);
++ spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_HLSL_FORCE_STORAGE_BUFFER_AS_UAV,
++ true);
++ spvc_compiler_install_compiler_options(d->hlslGen, options);
++
++ // D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT is 16, so we cannot have combined
++ // image/sampler bindings above 15. Problem is, the (SPIR-V) binding
++ // namespace is common with uniform buffers and other resources, and so
++ // having many uniform buffers and samplers can push the sampler binding to
++ // above 15 even when there are less than 16 samplers in total. So cannot
++ // just rely on SPIRV-Cross setting registers t<binding> and s<binding> for
++ // the SRV and sampler for each combined image/sampler. Instead, we want
++ // t<slot> and s<slot> where slot is 0-based, with its own namespace, and
++ // is then exposed in the native resource binding map so QRhi can map
++ // between (SPIR-V) bindings and D3D sampler slots.
++ const SpvExecutionModel stage = spvc_compiler_get_execution_model(d->hlslGen);
++ int regBinding = 0; // SRVs and samplers
++ const auto combinedImageSamplers = d->shaderDescription.combinedImageSamplers();
++ for (const QShaderDescription::InOutVariable &var : combinedImageSamplers) {
++ spvc_hlsl_resource_binding bindingMapping = {};
++ bindingMapping.stage = stage; // will be per-stage but we have a per-shader NativeResourceBindingMap so it's ok
++ bindingMapping.desc_set = var.descriptorSet;
++ bindingMapping.binding = var.binding;
++ bindingMapping.srv.register_space = 0;
++ bindingMapping.srv.register_binding = regBinding; // t0, t1, ...
++ bindingMapping.sampler.register_space = 0;
++ bindingMapping.sampler.register_binding = regBinding; // s0, s1, ...
++ spvc_compiler_hlsl_add_resource_binding(d->hlslGen, &bindingMapping);
++ nativeBindings->insert(var.binding, { regBinding, regBinding });
++ regBinding += var.arrayDims.isEmpty() ? 1 : var.arrayDims.first();
++ }
++ // In fact, with separate images and samplers added to the mix, the manual
++ // mapping specification is essential so that the HLSL nicely uses t0, t1,
++ // t2, ... and s0, s1, s2, ... for all the images and samplers incl. both
++ // combined and separate.
++ int firstSeparateImageReg = regBinding;
++ const auto separateImages = d->shaderDescription.separateImages();
++ for (const QShaderDescription::InOutVariable &var : separateImages) {
++ spvc_hlsl_resource_binding bindingMapping = {};
++ bindingMapping.stage = stage;
++ bindingMapping.desc_set = var.descriptorSet;
++ bindingMapping.binding = var.binding;
++ bindingMapping.srv.register_space = 0;
++ bindingMapping.srv.register_binding = regBinding; // tN, tN+1, ..., where N is the next reg after the combined image sampler ones
++ spvc_compiler_hlsl_add_resource_binding(d->hlslGen, &bindingMapping);
++ nativeBindings->insert(var.binding, { regBinding, -1 });
++ regBinding += var.arrayDims.isEmpty() ? 1 : var.arrayDims.first();
++ }
++ regBinding = firstSeparateImageReg;
++ const auto separateSamplers = d->shaderDescription.separateSamplers();
++ for (const QShaderDescription::InOutVariable &var : separateSamplers) {
++ spvc_hlsl_resource_binding bindingMapping = {};
++ bindingMapping.stage = stage;
++ bindingMapping.desc_set = var.descriptorSet;
++ bindingMapping.binding = var.binding;
++ bindingMapping.sampler.register_space = 0;
++ bindingMapping.sampler.register_binding = regBinding; // sN, sN+1, ..., where N is the next reg after the combined image sampler ones
++ spvc_compiler_hlsl_add_resource_binding(d->hlslGen, &bindingMapping);
++ nativeBindings->insert(var.binding, { regBinding, -1 });
++ regBinding += var.arrayDims.isEmpty() ? 1 : var.arrayDims.first();
++ }
++
++ regBinding = 0; // CBVs
++ // b0 is reserved for a push constant block, so that every stage agrees on
++ // the register regardless of how many uniform blocks it has.
++ const auto pushConstantBlocks = d->shaderDescription.pushConstantBlocks();
++ if (!pushConstantBlocks.isEmpty()) {
++ spvc_hlsl_resource_binding bindingMapping = {};
++ bindingMapping.stage = stage;
++ bindingMapping.desc_set = SPVC_HLSL_PUSH_CONSTANT_DESC_SET;
++ bindingMapping.binding = SPVC_HLSL_PUSH_CONSTANT_BINDING;
++ bindingMapping.cbv.register_space = 0;
++ bindingMapping.cbv.register_binding = regBinding;
++ spvc_compiler_hlsl_add_resource_binding(d->hlslGen, &bindingMapping);
++ shaderInfo->extraBufferBindings[QShaderPrivate::HlslPushConstantBufferBinding] = regBinding;
++ regBinding += 1;
++ }
++ const auto uniformBlocks = d->shaderDescription.uniformBlocks();
++ for (const QShaderDescription::UniformBlock &blk : uniformBlocks) {
++ spvc_hlsl_resource_binding bindingMapping = {};
++ bindingMapping.stage = stage;
++ bindingMapping.desc_set = blk.descriptorSet;
++ bindingMapping.binding = blk.binding;
++ bindingMapping.cbv.register_space = 0;
++ bindingMapping.cbv.register_binding = regBinding; // b0, b1, ...
++ spvc_compiler_hlsl_add_resource_binding(d->hlslGen, &bindingMapping);
++ nativeBindings->insert(blk.binding, { regBinding, -1 });
++ regBinding += 1;
++ }
++
++ // UAV registers live in the same name space as outputs, so they must be bound
++ // to at least outputVariables.size().
++ regBinding = d->shaderDescription.outputVariables().size();
++ const auto storageBlocks = d->shaderDescription.storageBlocks();
++ for (const QShaderDescription::StorageBlock &blk : storageBlocks) {
++ // readonly is also mapped to UAV due to FORCE_STORAGE_BUFFER_AS_UAV. (would be an SRV by default)
++ spvc_hlsl_resource_binding bindingMapping = {};
++ bindingMapping.stage = stage;
++ bindingMapping.desc_set = blk.descriptorSet;
++ bindingMapping.binding = blk.binding;
++ bindingMapping.uav.register_space = 0;
++ bindingMapping.uav.register_binding = regBinding; // u0, u1, ...
++ spvc_compiler_hlsl_add_resource_binding(d->hlslGen, &bindingMapping);
++ nativeBindings->insert(blk.binding, { regBinding, -1 });
++ regBinding += 1;
++ }
++ const auto storageImages = d->shaderDescription.storageImages();
++ for (const QShaderDescription::InOutVariable &var : storageImages) {
++ spvc_hlsl_resource_binding bindingMapping = {};
++ bindingMapping.stage = stage;
++ bindingMapping.desc_set = var.descriptorSet;
++ bindingMapping.binding = var.binding;
++ bindingMapping.uav.register_space = 0;
++ bindingMapping.uav.register_binding = regBinding; // u0, u1, ...
++ spvc_compiler_hlsl_add_resource_binding(d->hlslGen, &bindingMapping);
++ nativeBindings->insert(var.binding, { regBinding, -1 });
++ regBinding += 1;
++ }
++
++ const char *result = nullptr;
++ if (spvc_compiler_compile(d->hlslGen, &result) != SPVC_SUCCESS) {
++ d->spirvCrossErrorMsg = QString::fromUtf8(spvc_context_get_last_error_string(d->ctx));
++ return QByteArray();
++ }
++
++ return QByteArray(result);
++ }
++
++ QByteArray QSpirvShader::translateToMSL(int version,
++ MslFlags flags,
++ QShader::Stage stage,
++ QShader::NativeResourceBindingMap *nativeBindings,
++ QShader::NativeShaderInfo *shaderInfo,
++ const MultiViewInfo &multiViewInfo,
++ const TessellationInfo &tessInfo) const
++ {
++ d->spirvCrossErrorMsg.clear();
++
++ d->createCompiler(SPVC_BACKEND_MSL);
++ if (!d->mslGen)
++ return QByteArray();
++
++ spvc_compiler_options options = nullptr;
++ if (spvc_compiler_create_compiler_options(d->mslGen, &options) != SPVC_SUCCESS)
++ return QByteArray();
++
++ spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_MSL_VERSION,
++ SPVC_MAKE_MSL_VERSION(version / 10, version % 10, 0));
++
++ if (flags.testFlag(MslFlag::VertexAsCompute)) {
++ spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_MSL_VERTEX_FOR_TESSELLATION, 1);
++ spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_MSL_CAPTURE_OUTPUT_TO_BUFFER, 1);
++ spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_MSL_DISABLE_RASTERIZATION, 1);
++
++ spvc_msl_index_type indexType = SPVC_MSL_INDEX_TYPE_NONE;
++ if (flags.testFlag(MslFlag::WithUInt16Index))
++ indexType = SPVC_MSL_INDEX_TYPE_UINT16;
++ else if (flags.testFlag(MslFlag::WithUInt32Index))
++ indexType = SPVC_MSL_INDEX_TYPE_UINT32;
++ spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_MSL_VERTEX_INDEX_TYPE, indexType);
++ }
++
++ // for tessellation; matches the defaults; not sure how to query so just set them
++ uint spvIndicesBufferIndex = 21;
++ uint spvInBufferIndex = 22;
++ uint spvTessLevelBufferIndex = 26;
++ uint spvPatchOutBufferIndex = 27;
++ uint spvOutBufferIndex = 28;
++ uint spvIndirectParamsBufferIndex = 29;
++
++ spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_MSL_SHADER_INDEX_BUFFER_INDEX, spvIndicesBufferIndex);
++ spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_MSL_SHADER_INPUT_BUFFER_INDEX, spvInBufferIndex);
++ spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_MSL_SHADER_TESS_FACTOR_OUTPUT_BUFFER_INDEX, spvTessLevelBufferIndex);
++ spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_MSL_SHADER_PATCH_OUTPUT_BUFFER_INDEX, spvPatchOutBufferIndex);
++ spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_MSL_SHADER_OUTPUT_BUFFER_INDEX, spvOutBufferIndex);
++ spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_MSL_INDIRECT_PARAMS_BUFFER_INDEX, spvIndirectParamsBufferIndex);
++
++ // for buffer size buffer; matches defaults
++ uint spvBufferSizeBufferIndex = 25;
++ // for push constants; an automatically assigned index would land among the
++ // low ones, which is where the Metal backend packs the vertex buffers
++ uint spvPushConstantBufferIndex = 23;
++ spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_MSL_BUFFER_SIZE_BUFFER_INDEX, spvBufferSizeBufferIndex);
++
++ if (stage == QShader::TessellationControlStage) {
++ // required to get the kind of tess.control inputs we need
++ spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_MSL_MULTI_PATCH_WORKGROUP, 1);
++
++ // the execution mode needs to be known (e.g. to switch between
++ // MTLTriangleTessellationFactorsHalf and MTLQuadTessellationFactorsHalf)
++ SpvExecutionMode execMode = SpvExecutionModeTriangles;
++ switch (tessInfo.infoForTesc.mode) {
++ case QShaderDescription::QuadTessellationMode:
++ execMode = SpvExecutionModeQuads;
++ break;
++ case QShaderDescription::IsolineTessellationMode:
++ d->spirvCrossErrorMsg = QLatin1String("Isoline tessellation mode is not supported with Metal");
++ return QByteArray();
++ default:
++ break;
++ }
++ spvc_compiler_set_execution_mode(d->mslGen, execMode);
++ }
++
++ if (stage == QShader::TessellationEvaluationStage)
++ spvc_compiler_set_execution_mode_with_arguments(d->mslGen, SpvExecutionModeOutputVertices, uint(tessInfo.infoForTese.vertexCount), 0, 0);
++
++ uint spvViewMaskBufferIndex = 24;
++ const bool isMultiView = stage == QShader::VertexStage && multiViewInfo.viewCount > 1;
++ if (isMultiView) {
++ spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_MSL_MULTIVIEW, 1);
++ spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_MSL_MULTIVIEW_LAYERED_RENDERING, 1);
++ spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_MSL_VIEW_MASK_BUFFER_INDEX, spvViewMaskBufferIndex);
++ }
++
++ // Move images and samplers into a set of their own, make that set the
++ // argument buffer, and keep everything else bound individually. Pinning the
++ // argument buffer to index 0 matters: SPIRV-Cross starts the automatic
++ // buffer bindings at the argument buffer's index + 1.
++ const uint spvArgumentBufferSet = 1;
++ const uint spvArgumentBufferIndex = 0;
++ bool hasArgumentBuffer = false;
++ if (flags.testFlag(MslFlag::ArgumentBuffers)) {
++ spvc_resources resources;
++ if (spvc_compiler_create_shader_resources(d->mslGen, &resources) == SPVC_SUCCESS) {
++ for (spvc_resource_type type : { SPVC_RESOURCE_TYPE_SAMPLED_IMAGE,
++ SPVC_RESOURCE_TYPE_SEPARATE_IMAGE,
++ SPVC_RESOURCE_TYPE_SEPARATE_SAMPLERS,
++ SPVC_RESOURCE_TYPE_STORAGE_IMAGE })
++ {
++ const spvc_reflected_resource *resourceList = nullptr;
++ size_t resourceListCount = 0;
++ if (spvc_resources_get_resource_list_for_type(resources, type,
++ &resourceList, &resourceListCount) == SPVC_SUCCESS)
++ {
++ for (size_t i = 0; i < resourceListCount; ++i) {
++ spvc_compiler_set_decoration(d->mslGen, resourceList[i].id,
++ SpvDecorationDescriptorSet, spvArgumentBufferSet);
++ hasArgumentBuffer = true;
++ }
++ }
++ }
++ }
++ }
++ if (hasArgumentBuffer) {
++ spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_MSL_ARGUMENT_BUFFERS, 1);
++ spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_MSL_ARGUMENT_BUFFERS_TIER, 1); // Tier2
++ for (uint set = 0; set < 8; ++set) {
++ if (set != spvArgumentBufferSet)
++ spvc_compiler_msl_add_discrete_descriptor_set(d->mslGen, set);
++ }
++ spvc_msl_resource_binding argBufBinding;
++ spvc_msl_resource_binding_init(&argBufBinding);
++ argBufBinding.stage = spvc_compiler_get_execution_model(d->mslGen);
++ argBufBinding.desc_set = spvArgumentBufferSet;
++ argBufBinding.binding = SPVC_MSL_ARGUMENT_BUFFER_BINDING;
++ argBufBinding.msl_buffer = spvArgumentBufferIndex;
++ spvc_compiler_msl_add_resource_binding(d->mslGen, &argBufBinding);
++ }
++
++ const bool hasPushConstants = !d->shaderDescription.pushConstantBlocks().isEmpty();
++ if (hasPushConstants) {
++ spvc_msl_resource_binding pushConstantBinding;
++ spvc_msl_resource_binding_init(&pushConstantBinding);
++ pushConstantBinding.stage = spvc_compiler_get_execution_model(d->mslGen);
++ pushConstantBinding.desc_set = SPVC_MSL_PUSH_CONSTANT_DESC_SET;
++ pushConstantBinding.binding = SPVC_MSL_PUSH_CONSTANT_BINDING;
++ pushConstantBinding.msl_buffer = spvPushConstantBufferIndex;
++ spvc_compiler_msl_add_resource_binding(d->mslGen, &pushConstantBinding);
++ }
++
++ // leave platform set to macOS, it won't matter in practice (hopefully)
++ spvc_compiler_install_compiler_options(d->mslGen, options);
++
++ const char *result = nullptr;
++ if (spvc_compiler_compile(d->mslGen, &result) != SPVC_SUCCESS) {
++ d->spirvCrossErrorMsg = QString::fromUtf8(spvc_context_get_last_error_string(d->ctx));
++ return QByteArray();
++ }
++
++ if (nativeBindings) {
++ spvc_resources resources;
++ if (spvc_compiler_create_shader_resources(d->mslGen, &resources) == SPVC_SUCCESS) {
++ const spvc_reflected_resource *resourceList = nullptr;
++ size_t resourceListCount = 0;
++ // A nativeBinding of -1 means unused. This also fits
++ // *_get_automatic_resource_binding() which returns uint32_t(-1)
++ // when there is no binding, which can happen when the uniform
++ // block, sampler, etc. is not actively used in the shader. The map
++ // must always be complete, including a (binding -> -1) mapping for
++ // inactive resources as well. The second value of the pair is only
++ // relevant for combined image samplers, and is -1 otherwise.
++ if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_UNIFORM_BUFFER,
++ &resourceList, &resourceListCount) == SPVC_SUCCESS)
++ {
++ for (size_t i = 0; i < resourceListCount; ++i) {
++ unsigned binding = spvc_compiler_get_decoration(d->mslGen, resourceList[i].id, SpvDecorationBinding);
++ unsigned nativeBinding = spvc_compiler_msl_get_automatic_resource_binding(d->mslGen, resourceList[i].id);
++ nativeBindings->insert(int(binding), { int(nativeBinding), -1 });
++ }
++ }
++ if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_STORAGE_BUFFER,
++ &resourceList, &resourceListCount) == SPVC_SUCCESS)
++ {
++ for (size_t i = 0; i < resourceListCount; ++i) {
++ unsigned binding = spvc_compiler_get_decoration(d->mslGen, resourceList[i].id, SpvDecorationBinding);
++ unsigned nativeBinding = spvc_compiler_msl_get_automatic_resource_binding(d->mslGen, resourceList[i].id);
++ nativeBindings->insert(int(binding), { int(nativeBinding), -1 });
++ }
++ }
++ if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_SAMPLED_IMAGE,
++ &resourceList, &resourceListCount) == SPVC_SUCCESS)
++ {
++ for (size_t i = 0; i < resourceListCount; ++i) {
++ unsigned binding = spvc_compiler_get_decoration(d->mslGen, resourceList[i].id, SpvDecorationBinding);
++ unsigned nativeTextureBinding = spvc_compiler_msl_get_automatic_resource_binding(d->mslGen, resourceList[i].id);
++ unsigned nativeSamplerBinding = spvc_compiler_msl_get_automatic_resource_binding_secondary(d->mslGen, resourceList[i].id);
++ nativeBindings->insert(int(binding), { int(nativeTextureBinding), int(nativeSamplerBinding) });
++ }
++ }
++ if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_SEPARATE_IMAGE,
++ &resourceList, &resourceListCount) == SPVC_SUCCESS)
++ {
++ for (size_t i = 0; i < resourceListCount; ++i) {
++ unsigned binding = spvc_compiler_get_decoration(d->mslGen, resourceList[i].id, SpvDecorationBinding);
++ unsigned nativeTextureBinding = spvc_compiler_msl_get_automatic_resource_binding(d->mslGen, resourceList[i].id);
++ nativeBindings->insert(int(binding), { int(nativeTextureBinding), -1 });
++ }
++ }
++ if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_SEPARATE_SAMPLERS,
++ &resourceList, &resourceListCount) == SPVC_SUCCESS)
++ {
++ for (size_t i = 0; i < resourceListCount; ++i) {
++ unsigned binding = spvc_compiler_get_decoration(d->mslGen, resourceList[i].id, SpvDecorationBinding);
++ unsigned nativeSamplerBinding = spvc_compiler_msl_get_automatic_resource_binding(d->mslGen, resourceList[i].id);
++ nativeBindings->insert(int(binding), { int(nativeSamplerBinding), -1 });
++ }
++ }
++ if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_SAMPLED_IMAGE,
++ &resourceList, &resourceListCount) == SPVC_SUCCESS)
++ {
++ for (size_t i = 0; i < resourceListCount; ++i) {
++ unsigned binding = spvc_compiler_get_decoration(d->mslGen, resourceList[i].id, SpvDecorationBinding);
++ unsigned nativeTextureBinding = spvc_compiler_msl_get_automatic_resource_binding(d->mslGen, resourceList[i].id);
++ unsigned nativeSamplerBinding = spvc_compiler_msl_get_automatic_resource_binding_secondary(d->mslGen, resourceList[i].id);
++ nativeBindings->insert(int(binding), { int(nativeTextureBinding), int(nativeSamplerBinding) });
++ }
++ }
++ if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_STORAGE_IMAGE,
++ &resourceList, &resourceListCount) == SPVC_SUCCESS)
++ {
++ for (size_t i = 0; i < resourceListCount; ++i) {
++ unsigned binding = spvc_compiler_get_decoration(d->mslGen, resourceList[i].id, SpvDecorationBinding);
++ unsigned nativeBinding = spvc_compiler_msl_get_automatic_resource_binding(d->mslGen, resourceList[i].id);
++ nativeBindings->insert(int(binding), { int(nativeBinding), -1 });
++ }
++ }
++ }
++ }
++
++ if (spvc_compiler_msl_needs_swizzle_buffer(d->mslGen))
++ qWarning("Translated Metal shader needs swizzle buffer, this is unexpected");
++
++ if (spvc_compiler_msl_needs_buffer_size_buffer(d->mslGen))
++ shaderInfo->extraBufferBindings[QShaderPrivate::MslBufferSizeBufferBinding] = spvBufferSizeBufferIndex;
++
++ if (isMultiView)
++ shaderInfo->extraBufferBindings[QShaderPrivate::MslMultiViewMaskBufferBinding] = spvViewMaskBufferIndex;
++
++ if (hasArgumentBuffer)
++ shaderInfo->extraBufferBindings[QShaderPrivate::MslArgumentBufferBinding] = spvArgumentBufferIndex;
++
++ // SPIRV-Cross drops the block from the entry point when nothing reads it,
++ // in which case there is no buffer to report.
++ if (hasPushConstants
++ && spvc_compiler_msl_is_resource_used(d->mslGen, spvc_compiler_get_execution_model(d->mslGen),
++ SPVC_MSL_PUSH_CONSTANT_DESC_SET, SPVC_MSL_PUSH_CONSTANT_BINDING))
++ {
++ shaderInfo->extraBufferBindings[QShaderPrivate::MslPushConstantBufferBinding] = spvPushConstantBufferIndex;
++ }
++
++ // (Aim to) only store extraBufferBindings entries for things that really
++ // are present, because the presence of a key can already trigger certain
++ // behavior during rendering. (e.g. generating and exposing the
++ // corresponding implicit buffer)
++ switch (spvc_compiler_get_execution_model(d->mslGen)) {
++ case SpvExecutionModelVertex:
++ if (flags.testFlag(MslFlag::VertexAsCompute)) {
++ if (spvc_compiler_msl_needs_output_buffer(d->mslGen))
++ shaderInfo->extraBufferBindings[QShaderPrivate::MslTessVertTescOutputBufferBinding] = spvOutBufferIndex;
++ if (flags.testFlag(MslFlag::WithUInt16Index) || flags.testFlag(MslFlag::WithUInt32Index)) {
++ // not given that this buffer is really used in the code,
++ // depends on e.g. the usage of gl_VertexIndex, but not sure how
++ // to tell that here
++ shaderInfo->extraBufferBindings[QShaderPrivate::MslTessVertIndicesBufferBinding] = spvIndicesBufferIndex;
++ }
++ }
++ break;
++ case SpvExecutionModelTessellationControl:
++ shaderInfo->extraBufferBindings[QShaderPrivate::MslTessTescInputBufferBinding] = spvInBufferIndex;
++ shaderInfo->extraBufferBindings[QShaderPrivate::MslTessTescTessLevelBufferBinding] = spvTessLevelBufferIndex;
++ shaderInfo->extraBufferBindings[QShaderPrivate::MslTessTescParamsBufferBinding] = spvIndirectParamsBufferIndex;
++ if (spvc_compiler_msl_needs_output_buffer(d->mslGen))
++ shaderInfo->extraBufferBindings[QShaderPrivate::MslTessVertTescOutputBufferBinding] = spvOutBufferIndex;
++ if (spvc_compiler_msl_needs_patch_output_buffer(d->mslGen))
++ shaderInfo->extraBufferBindings[QShaderPrivate::MslTessTescPatchOutputBufferBinding] = spvPatchOutBufferIndex;
++ break;
++ default:
++ break;
++ }
++
++ return QByteArray(result);
++ }
++
++ QString QSpirvShader::translationErrorMessage() const
++ {
++ return d->spirvCrossErrorMsg;
++ }
++
++ QT_END_NAMESPACE