KhronosGroup / MoltenVK

MoltenVK is a Vulkan Portability implementation. It layers a subset of the high-performance, industry-standard Vulkan graphics and compute API over Apple's Metal graphics framework, enabling Vulkan applications to run on macOS, iOS and tvOS.
Apache License 2.0
4.79k stars 423 forks source link

Incorrect generation of spvDescriptorArray in 1.3.268.1 #2052

Closed rokuz closed 11 months ago

rokuz commented 11 months ago

I started getting the following error after updated from 1.3.261.1 to 1.3.268.1:

[mvk-error] VK_ERROR_INITIALIZATION_FAILED: Shader library compile failed (Error code 3):
program_source:53:117: error: no template named 'spvDescriptorArray'
float4 textureBindless2D(thread const uint& textureid, thread const uint& samplerid, thread const float2& uv, const spvDescriptorArray<texture2d<float>> kTextures2D, const spvDescriptorArray<sampler> kSamplers)
                                                                                                                    ^
program_source:53:173: error: no template named 'spvDescriptorArray'
float4 textureBindless2D(thread const uint& textureid, thread const uint& samplerid, thread const float2& uv, const spvDescriptorArray<texture2d<float>> kTextures2D, const spvDescriptorArray<sampler> kSamplers)
                                                                                                                                                                            ^
.
[mvk-error] VK_ERROR_INVALID_SHADER_NV: Fragment shader function could not be compiled into pipeline. See previous logged error.
Assertion failed: (false), function Assert, file LVK.cpp, line 140.
Compiler failed to build request

Problem:

New MoltenVK (SPIR-V Cross in it?) generates spvDescriptorArray around arrays of textures and samplers but do not define spvDescriptorArray itself.

// Before update
static inline __attribute__((always_inline))
float4 textureBindless2D(thread const uint& textureid, 
  thread const uint& samplerid, 
  thread const float2& uv, 
  constant array<texture2d<float>, 16>& kTextures2D, 
  constant array<sampler, 16>& kSamplers)
{
    return kTextures2D[textureid].sample(kSamplers[samplerid], uv);
}

// After update: No spvDescriptorArray in MSL output
static inline __attribute__((always_inline))
float4 textureBindless2D(thread const uint& textureid, 
  thread const uint& samplerid, 
  thread const float2& uv, 
  const spvDescriptorArray<texture2d<float>> kTextures2D, 
  const spvDescriptorArray<sampler> kSamplers)
{
    return kTextures2D[textureid].sample(kSamplers[samplerid], uv);
}

Full outputs:

Original GLSL:

#version 460
#extension GL_EXT_buffer_reference_uvec2 : require
#extension GL_EXT_debug_printf : enable
#extension GL_EXT_nonuniform_qualifier : require
#extension GL_EXT_samplerless_texture_functions : require
#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require

layout (set = 0, binding = 0) uniform texture2D kTextures2D[];
layout (set = 1, binding = 0) uniform texture3D kTextures3D[];
layout (set = 2, binding = 0) uniform textureCube kTexturesCube[];
layout (set = 0, binding = 1) uniform sampler kSamplers[];
layout (set = 1, binding = 1) uniform samplerShadow kSamplersShadow[];

vec4 textureBindless2D(uint textureid, uint samplerid, vec2 uv) {
  return texture(sampler2D(kTextures2D[textureid], kSamplers[samplerid]), uv);
}
vec4 textureBindless2DLod(uint textureid, uint samplerid, vec2 uv, float lod) {
  return textureLod(sampler2D(kTextures2D[textureid], kSamplers[samplerid]), uv, lod);
}
float textureBindless2DShadow(uint textureid, uint samplerid, vec3 uvw) {
  return texture(sampler2DShadow(kTextures2D[textureid], kSamplersShadow[samplerid]), uvw);
}
ivec2 textureBindlessSize2D(uint textureid) {
  return textureSize(kTextures2D[textureid], 0);
}
vec4 textureBindlessCube(uint textureid, uint samplerid, vec3 uvw) {
  return texture(samplerCube(kTexturesCube[textureid], kSamplers[samplerid]), uvw);
}
vec4 textureBindlessCubeLod(uint textureid, uint samplerid, vec3 uvw, float lod) {
  return textureLod(samplerCube(kTexturesCube[textureid], kSamplers[samplerid]), uvw, lod);
}
int textureBindlessQueryLevels2D(uint textureid) {
  return textureQueryLevels(kTextures2D[textureid]);
}

layout (location=0) in vec3 color;
layout (location=1) in vec2 uv;
layout (location=0) out vec4 out_FragColor;

layout(std430, buffer_reference) readonly buffer PerFrame {
  mat4 proj;
  mat4 view;
  uint texture0;
  uint texture1;
  uint sampler0;
};

layout(push_constant) uniform constants {
  PerFrame perFrame;
} pc;

void main() {
  vec4 t0 = textureBindless2D(pc.perFrame.texture0, pc.perFrame.sampler0, 2.0*uv);
  vec4 t1 = textureBindless2D(pc.perFrame.texture1, pc.perFrame.sampler0, uv);
  out_FragColor = vec4(color * (t0.rgb + t1.rgb), 1.0);
};

SPIR-V:

; SPIR-V
; Version: 1.6
; Generator: Khronos Glslang Reference Front End; 11
; Bound: 116
; Schema: 0
               OpCapability Shader
               OpCapability RuntimeDescriptorArray
               OpCapability PhysicalStorageBufferAddresses
          %2 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel PhysicalStorageBuffer64 GLSL450
               OpEntryPoint Fragment %5 "main" %22 %30 %48 %60 %88 %91 %108 %112 %115
               OpExecutionMode %5 OriginUpperLeft
          %1 = OpString ""
               OpSource GLSL 460 %1
               OpSourceExtension "GL_EXT_buffer_reference"
               OpSourceExtension "GL_EXT_buffer_reference_uvec2"
               OpSourceExtension "GL_EXT_debug_printf"
               OpSourceExtension "GL_EXT_nonuniform_qualifier"
               OpSourceExtension "GL_EXT_samplerless_texture_functions"
               OpSourceExtension "GL_EXT_shader_explicit_arithmetic_types_float16"
               OpName %5 "main"
               OpName %17 "textureBindless2D(u1;u1;vf2;"
               OpName %14 "textureid"
               OpName %15 "samplerid"
               OpName %16 "uv"
               OpName %22 "kTextures2D"
               OpName %30 "kSamplers"
               OpName %42 "t0"
               OpName %44 "constants"
               OpMemberName %44 0 "perFrame"
               OpName %46 "PerFrame"
               OpMemberName %46 0 "proj"
               OpMemberName %46 1 "view"
               OpMemberName %46 2 "texture0"
               OpMemberName %46 3 "texture1"
               OpMemberName %46 4 "sampler0"
               OpName %48 "pc"
               OpName %60 "uv"
               OpName %63 "param"
               OpName %67 "param"
               OpName %70 "param"
               OpName %72 "t1"
               OpName %78 "param"
               OpName %81 "param"
               OpName %84 "param"
               OpName %88 "out_FragColor"
               OpName %91 "color"
               OpName %108 "kTextures3D"
               OpName %112 "kTexturesCube"
               OpName %115 "kSamplersShadow"
               OpModuleProcessed "client vulkan100"
               OpModuleProcessed "target-env spirv1.6"
               OpModuleProcessed "target-env vulkan1.3"
               OpModuleProcessed "entry-point main"
               OpModuleProcessed "client vulkan100"
               OpModuleProcessed "target-env spirv1.6"
               OpModuleProcessed "target-env vulkan1.3"
               OpModuleProcessed "entry-point main"
               OpDecorate %22 DescriptorSet 0
               OpDecorate %22 Binding 0
               OpDecorate %30 DescriptorSet 0
               OpDecorate %30 Binding 1
               OpMemberDecorate %44 0 Offset 0
               OpDecorate %44 Block
               OpMemberDecorate %46 0 ColMajor
               OpMemberDecorate %46 0 NonWritable
               OpMemberDecorate %46 0 Offset 0
               OpMemberDecorate %46 0 MatrixStride 16
               OpMemberDecorate %46 1 ColMajor
               OpMemberDecorate %46 1 NonWritable
               OpMemberDecorate %46 1 Offset 64
               OpMemberDecorate %46 1 MatrixStride 16
               OpMemberDecorate %46 2 NonWritable
               OpMemberDecorate %46 2 Offset 128
               OpMemberDecorate %46 3 NonWritable
               OpMemberDecorate %46 3 Offset 132
               OpMemberDecorate %46 4 NonWritable
               OpMemberDecorate %46 4 Offset 136
               OpDecorate %46 Block
               OpDecorate %60 Location 1
               OpDecorate %88 Location 0
               OpDecorate %91 Location 0
               OpDecorate %108 DescriptorSet 1
               OpDecorate %108 Binding 0
               OpDecorate %112 DescriptorSet 2
               OpDecorate %112 Binding 0
               OpDecorate %115 DescriptorSet 1
               OpDecorate %115 Binding 1
          %3 = OpTypeVoid
          %4 = OpTypeFunction %3
          %7 = OpTypeInt 32 0
          %8 = OpTypePointer Function %7
          %9 = OpTypeFloat 32
         %10 = OpTypeVector %9 2
         %11 = OpTypePointer Function %10
         %12 = OpTypeVector %9 4
         %13 = OpTypeFunction %12 %8 %8 %11
         %19 = OpTypeImage %9 2D 0 0 0 1 Unknown
         %20 = OpTypeRuntimeArray %19
         %21 = OpTypePointer UniformConstant %20
         %22 = OpVariable %21 UniformConstant
         %24 = OpTypePointer UniformConstant %19
         %27 = OpTypeSampler
         %28 = OpTypeRuntimeArray %27
         %29 = OpTypePointer UniformConstant %28
         %30 = OpVariable %29 UniformConstant
         %32 = OpTypePointer UniformConstant %27
         %35 = OpTypeSampledImage %19
         %41 = OpTypePointer Function %12
               OpTypeForwardPointer %43 PhysicalStorageBuffer
         %44 = OpTypeStruct %43
         %45 = OpTypeMatrix %12 4
         %46 = OpTypeStruct %45 %45 %7 %7 %7
         %43 = OpTypePointer PhysicalStorageBuffer %46
         %47 = OpTypePointer PushConstant %44
         %48 = OpVariable %47 PushConstant
         %49 = OpTypeInt 32 1
         %50 = OpConstant %49 0
         %51 = OpTypePointer PushConstant %43
         %54 = OpConstant %49 2
         %57 = OpConstant %49 4
         %58 = OpConstant %9 2
         %59 = OpTypePointer Input %10
         %60 = OpVariable %59 Input
         %64 = OpTypePointer PhysicalStorageBuffer %7
         %75 = OpConstant %49 3
         %87 = OpTypePointer Output %12
         %88 = OpVariable %87 Output
         %89 = OpTypeVector %9 3
         %90 = OpTypePointer Input %89
         %91 = OpVariable %90 Input
         %99 = OpConstant %9 1
        %104 = OpTypeImage %9 3D 0 0 0 1 Unknown
        %105 = OpConstant %7 1
        %106 = OpTypeArray %104 %105
        %107 = OpTypePointer UniformConstant %106
        %108 = OpVariable %107 UniformConstant
        %109 = OpTypeImage %9 Cube 0 0 0 1 Unknown
        %110 = OpTypeRuntimeArray %109
        %111 = OpTypePointer UniformConstant %110
        %112 = OpVariable %111 UniformConstant
        %113 = OpTypeRuntimeArray %27
        %114 = OpTypePointer UniformConstant %113
        %115 = OpVariable %114 UniformConstant
               OpLine %1 53 11
          %5 = OpFunction %3 None %4
          %6 = OpLabel
         %42 = OpVariable %41 Function
         %63 = OpVariable %8 Function
         %67 = OpVariable %8 Function
         %70 = OpVariable %11 Function
         %72 = OpVariable %41 Function
         %78 = OpVariable %8 Function
         %81 = OpVariable %8 Function
         %84 = OpVariable %11 Function
               OpLine %1 54 0
         %52 = OpAccessChain %51 %48 %50
         %53 = OpLoad %43 %52
         %55 = OpAccessChain %51 %48 %50
         %56 = OpLoad %43 %55
         %61 = OpLoad %10 %60
         %62 = OpVectorTimesScalar %10 %61 %58
         %65 = OpAccessChain %64 %53 %54
         %66 = OpLoad %7 %65 Aligned 16
               OpStore %63 %66
         %68 = OpAccessChain %64 %56 %57
         %69 = OpLoad %7 %68 Aligned 8
               OpStore %67 %69
               OpStore %70 %62
         %71 = OpFunctionCall %12 %17 %63 %67 %70
               OpStore %42 %71
               OpLine %1 55 0
         %73 = OpAccessChain %51 %48 %50
         %74 = OpLoad %43 %73
         %76 = OpAccessChain %51 %48 %50
         %77 = OpLoad %43 %76
         %79 = OpAccessChain %64 %74 %75
         %80 = OpLoad %7 %79 Aligned 4
               OpStore %78 %80
         %82 = OpAccessChain %64 %77 %57
         %83 = OpLoad %7 %82 Aligned 8
               OpStore %81 %83
         %85 = OpLoad %10 %60
               OpStore %84 %85
         %86 = OpFunctionCall %12 %17 %78 %81 %84
               OpStore %72 %86
               OpLine %1 56 0
         %92 = OpLoad %89 %91
         %93 = OpLoad %12 %42
         %94 = OpVectorShuffle %89 %93 %93 0 1 2
         %95 = OpLoad %12 %72
         %96 = OpVectorShuffle %89 %95 %95 0 1 2
         %97 = OpFAdd %89 %94 %96
         %98 = OpFMul %89 %92 %97
        %100 = OpCompositeExtract %9 %98 0
        %101 = OpCompositeExtract %9 %98 1
        %102 = OpCompositeExtract %9 %98 2
        %103 = OpCompositeConstruct %12 %100 %101 %102 %99
               OpStore %88 %103
               OpReturn
               OpFunctionEnd
               OpLine %1 15 69
         %17 = OpFunction %12 None %13
         %14 = OpFunctionParameter %8
         %15 = OpFunctionParameter %8
         %16 = OpFunctionParameter %11
         %18 = OpLabel
               OpLine %1 16 0
         %23 = OpLoad %7 %14
         %25 = OpAccessChain %24 %22 %23
         %26 = OpLoad %19 %25
         %31 = OpLoad %7 %15
         %33 = OpAccessChain %32 %30 %31
         %34 = OpLoad %27 %33
         %36 = OpSampledImage %35 %26 %34
         %37 = OpLoad %10 %16
         %38 = OpImageSampleImplicitLod %12 %36 %37
               OpReturnValue %38
               OpFunctionEnd

MSL generated in 1.3.268.1 :

#pragma clang diagnostic ignored "-Wmissing-prototypes"

#include <metal_stdlib>
#include <simd/simd.h>

using namespace metal;

struct PerFrame;

struct constants
{
    device PerFrame* perFrame;
};

struct PerFrame
{
    float4x4 proj;
    float4x4 view;
    uint texture0;
    uint texture1;
    uint sampler0;
};

struct spvDescriptorSetBuffer0
{
    array<texture2d<float>, 16> kTextures2D [[id(0)]];
    array<sampler, 16> kSamplers [[id(16)]];
};

struct spvDescriptorSetBuffer1
{
    array<texture3d<float>, 1> kTextures3D [[id(0)]];
    array<sampler, 16> kSamplersShadow [[id(16)]];
};

struct spvDescriptorSetBuffer2
{
    array<texturecube<float>, 16> kTexturesCube [[id(0)]];
};

struct main0_out
{
    float4 out_FragColor [[color(0)]];
};

struct main0_in
{
    float3 color [[user(locn0)]];
    float2 uv [[user(locn1)]];
};

static inline __attribute__((always_inline))
float4 textureBindless2D(thread const uint& textureid, thread const uint& samplerid, thread const float2& uv, const spvDescriptorArray<texture2d<float>> kTextures2D, const spvDescriptorArray<sampler> kSamplers)
{
    return kTextures2D[textureid].sample(kSamplers[samplerid], uv);
}

fragment main0_out main0(main0_in in [[stage_in]], constant spvDescriptorSetBuffer0& spvDescriptorSet0 [[buffer(0)]], constant spvDescriptorSetBuffer1& spvDescriptorSet1 [[buffer(1)]], constant spvDescriptorSetBuffer2& spvDescriptorSet2 [[buffer(2)]], constant constants& pc [[buffer(8)]])
{
    main0_out out = {};
    uint param = pc.perFrame->texture0;
    uint param_1 = pc.perFrame->sampler0;
    float2 param_2 = in.uv * 2.0;
    float4 t0 = textureBindless2D(param, param_1, param_2, spvDescriptorSet0.kTextures2D, spvDescriptorSet0.kSamplers);
    uint param_3 = pc.perFrame->texture1;
    uint param_4 = pc.perFrame->sampler0;
    float2 param_5 = in.uv;
    float4 t1 = textureBindless2D(param_3, param_4, param_5, spvDescriptorSet0.kTextures2D, spvDescriptorSet0.kSamplers);
    out.out_FragColor = float4(in.color * (t0.xyz + t1.xyz), 1.0);
    return out;
}

MSL generated in 1.3.261.1 :

#pragma clang diagnostic ignored "-Wmissing-prototypes"

#include <metal_stdlib>
#include <simd/simd.h>

using namespace metal;

struct PerFrame;

struct constants
{
    device PerFrame* perFrame;
};

struct PerFrame
{
    float4x4 proj;
    float4x4 view;
    uint texture0;
    uint texture1;
    uint sampler0;
};

struct spvDescriptorSetBuffer0
{
    array<texture2d<float>, 16> kTextures2D [[id(0)]];
    array<sampler, 16> kSamplers [[id(16)]];
};

struct spvDescriptorSetBuffer1
{
    array<texture3d<float>, 1> kTextures3D [[id(0)]];
    array<sampler, 16> kSamplersShadow [[id(16)]];
};

struct spvDescriptorSetBuffer2
{
    array<texturecube<float>, 16> kTexturesCube [[id(0)]];
};

struct main0_out
{
    float4 out_FragColor [[color(0)]];
};

struct main0_in
{
    float3 color [[user(locn0)]];
    float2 uv [[user(locn1)]];
};

static inline __attribute__((always_inline))
float4 textureBindless2D(thread const uint& textureid, thread const uint& samplerid, thread const float2& uv, constant array<texture2d<float>, 16>& kTextures2D, constant array<sampler, 16>& kSamplers)
{
    return kTextures2D[textureid].sample(kSamplers[samplerid], uv);
}

fragment main0_out main0(main0_in in [[stage_in]], constant spvDescriptorSetBuffer0& spvDescriptorSet0 [[buffer(0)]], constant spvDescriptorSetBuffer1& spvDescriptorSet1 [[buffer(1)]], constant spvDescriptorSetBuffer2& spvDescriptorSet2 [[buffer(2)]], constant constants& pc [[buffer(8)]])
{
    main0_out out = {};
    uint param = pc.perFrame->texture0;
    uint param_1 = pc.perFrame->sampler0;
    float2 param_2 = in.uv * 2.0;
    float4 t0 = textureBindless2D(param, param_1, param_2, spvDescriptorSet0.kTextures2D, spvDescriptorSet0.kSamplers);
    uint param_3 = pc.perFrame->texture1;
    uint param_4 = pc.perFrame->sampler0;
    float2 param_5 = in.uv;
    float4 t1 = textureBindless2D(param_3, param_4, param_5, spvDescriptorSet0.kTextures2D, spvDescriptorSet0.kSamplers);
    out.out_FragColor = float4(in.color * (t0.xyz + t1.xyz), 1.0);
    return out;
}
cdavis5e commented 11 months ago

Yes, this is a SPIRV-Cross issue. It looks like add_spv_func_and_recompile(SPVFUncImplVariableDescriptorArray) isn't getting called when it should be.

rokuz commented 11 months ago

@cdavis5e Thanks for quick answer!

I built SPIR-V Cross from the commit that used in the latest MoltenVK and launched it with the following args:

--msl-version 30000 --msl-argument-buffer-tier 1 --msl-argument-buffers

It generated MSL that looks correct. At least it contains spvDescriptor and spvDescriptorArray definitions and declarations in spvDescriptorSetBuffer0 instead of sized arrays that I got from MoltenVK. Can this problem be connected with wrong parameters for SPIR-V Cross in MoltenVK? But not with SPIR-V Cross itself? Like wrong argument buffer tier?

Generated MSL:

#pragma clang diagnostic ignored "-Wmissing-prototypes"

#include <metal_stdlib>
#include <simd/simd.h>

using namespace metal;

template<typename T>
struct spvDescriptor
{
    T value;
};

template<typename T>
struct spvDescriptorArray
{
    spvDescriptorArray(const device spvDescriptor<T>* ptr) : ptr(ptr)
    {
    }
    const device T& operator [] (size_t i) const
    {
        return ptr[i].value;
    }
    const device spvDescriptor<T>* ptr;
};

struct PerFrame;

struct constants
{
    device PerFrame* perFrame;
};

struct PerFrame
{
    float4x4 proj;
    float4x4 view;
    uint texture0;
    uint texture1;
    uint sampler0;
};

struct spvDescriptorSetBuffer0
{
    const device spvDescriptor<texture2d<float>>* kTextures2D [[id(0)]];
    const spvDescriptorArray<sampler> kSamplers [[id(0)]];
};

struct main0_out
{
    float4 out_FragColor [[color(0)]];
};

struct main0_in
{
    float3 color [[user(locn0)]];
    float2 uv [[user(locn1)]];
};

static inline __attribute__((always_inline))
float4 textureBindless2D(thread const uint& textureid, thread const uint& samplerid, thread const float2& uv, const spvDescriptorArray<texture2d<float>> kTextures2D, const spvDescriptorArray<sampler> kSamplers)
{
    return kTextures2D[textureid].sample(kSamplers[samplerid], uv);
}

fragment main0_out main0(main0_in in [[stage_in]], constant spvDescriptorSetBuffer0& spvDescriptorSet0 [[buffer(0)]], constant constants& pc [[buffer(1)]])
{
    main0_out out = {};
    uint param = pc.perFrame->texture0;
    uint param_1 = pc.perFrame->sampler0;
    float2 param_2 = in.uv * 2.0;
    float4 t0 = textureBindless2D(param, param_1, param_2, spvDescriptorSet0.kTextures2D, spvDescriptorSet0.kSamplers);
    uint param_3 = pc.perFrame->texture1;
    uint param_4 = pc.perFrame->sampler0;
    float2 param_5 = in.uv;
    float4 t1 = textureBindless2D(param_3, param_4, param_5, spvDescriptorSet0.kTextures2D, spvDescriptorSet0.kSamplers);
    out.out_FragColor = float4(in.color * (t0.xyz + t1.xyz), 1.0);
    return out;
}
cdavis5e commented 11 months ago

Can this problem be connected with wrong parameters for SPIR-V Cross in MoltenVK?

Well, since standalone SPIRV-Cross works, that would be the most likely explanation.

rokuz commented 11 months ago

@cdavis5e After comparing standalone SPIR-V Cross and MoltenVK's one I found that the following code's fragment gives difference in MSL output:

        for (auto& rb : shaderConfig.resourceBindings) {
            auto& rbb = rb.resourceBinding;
            pMSLCompiler->add_msl_resource_binding(rbb);

            if (rb.requiresConstExprSampler) {
                pMSLCompiler->remap_constexpr_sampler_by_binding(rbb.desc_set, rbb.binding, rb.constExprSampler);
            }
        }

So if we have resource binding that declares textures/samplers array with size 16, it makes runtime arrays not runtime any more and SPIR-V Cross generates

struct spvDescriptorSetBuffer0
{
    array<texture2d<float>, 16> kTextures2D [[id(0)]];
    array<sampler, 16> kSamplers [[id(16)]];
};

instead of

struct spvDescriptorSetBuffer0
{
    const device spvDescriptor<texture2d<float>>* kTextures2D [[id(0)]];
    const spvDescriptorArray<sampler> kSamplers [[id(1)]];
};

I hope it's helpful. Does it make sense to fix it on MoltenVK's side - pass size = 1 for runtime arrays to prevent array<..., 16> generation?

billhollings commented 11 months ago

I hope it's helpful.

Thanks for all the research and debug info.

I'm looking into this now.

billhollings commented 11 months ago

Thanks for reporting this, and for including such detailed research and debugging info!

struct spvDescriptorSetBuffer0
{
    const device spvDescriptor<texture2d<float>>* kTextures2D [[id(0)]];
    const spvDescriptorArray<sampler> kSamplers [[id(1)]];
};

I hope it's helpful. Does it make sense to fix it on MoltenVK's side - pass size = 1 for runtime arrays to prevent array<..., 16> generation?

Unfortunately not. The two runtime arrays above each have 16 members (according to the Vulkan descriptor layouts) and are included in a single descriptor set, so the shader really does need to know how many descriptors to expect in all but the last runtime array, and what slot each array starts in.

The regression is a result of SPRIV-Cross code recently introduced that assumed each runtime array is in a separate Metal argument buffer. MoltenVK does not make that assumption, because it works with Vulkan descriptors, and assigns each descriptor set to a Metal Argument Buffer.

It would require nested Metal Argument Buffers, and take a significant effort within both SPIRV-Cross and MoltenVK to redirect access to multiple runtime arrays within a single descriptor set to a separate Metal Argument Buffer for each runtime array.

Maybe someday. But for now, I've submitted a PR to SPIRV-Cross to fix the regression, and restore the previous expected MSL.

Once that PR gets pulled into SPIRV-Cross, I'll issue a MoltenVK PR to incorporate it.

billhollings commented 11 months ago

PR #2060 fixes this. Please retest with latest MoltenVK and close this issue if it repairs the problem.

rokuz commented 11 months ago

@billhollings Thank you for the quick fix! I validated the change after building MoltenVK from the main branch. The problem is fixed.