diff --git a/3rdparty/spirv-cross/spirv_common.hpp b/3rdparty/spirv-cross/spirv_common.hpp index 128c41627..a7a84188f 100644 --- a/3rdparty/spirv-cross/spirv_common.hpp +++ b/3rdparty/spirv-cross/spirv_common.hpp @@ -302,8 +302,20 @@ struct Instruction { uint16_t op = 0; uint16_t count = 0; + // If offset is 0 (not a valid offset into the instruction stream), + // we have an instruction stream which is embedded in the object. uint32_t offset = 0; uint32_t length = 0; + + inline bool is_embedded() const + { + return offset == 0; + } +}; + +struct EmbeddedInstruction : Instruction +{ + SmallVector ops; }; enum Types diff --git a/3rdparty/spirv-cross/spirv_cross.hpp b/3rdparty/spirv-cross/spirv_cross.hpp index 944e855b2..84e23ca30 100644 --- a/3rdparty/spirv-cross/spirv_cross.hpp +++ b/3rdparty/spirv-cross/spirv_cross.hpp @@ -513,9 +513,18 @@ protected: if (!instr.length) return nullptr; - if (instr.offset + instr.length > ir.spirv.size()) - SPIRV_CROSS_THROW("Compiler::stream() out of range."); - return &ir.spirv[instr.offset]; + if (instr.is_embedded()) + { + auto &embedded = static_cast(instr); + assert(embedded.ops.size() == instr.length); + return embedded.ops.data(); + } + else + { + if (instr.offset + instr.length > ir.spirv.size()) + SPIRV_CROSS_THROW("Compiler::stream() out of range."); + return &ir.spirv[instr.offset]; + } } ParsedIR ir; diff --git a/3rdparty/spirv-cross/spirv_glsl.cpp b/3rdparty/spirv-cross/spirv_glsl.cpp index ca8267ec1..31ff85042 100644 --- a/3rdparty/spirv-cross/spirv_glsl.cpp +++ b/3rdparty/spirv-cross/spirv_glsl.cpp @@ -40,6 +40,13 @@ using namespace spv; using namespace SPIRV_CROSS_NAMESPACE; using namespace std; +enum ExtraSubExpressionType +{ + // Create masks above any legal ID range to allow multiple address spaces into the extra_sub_expressions map. + EXTRA_SUB_EXPRESSION_TYPE_STREAM_OFFSET = 0x10000000, + EXTRA_SUB_EXPRESSION_TYPE_AUX = 0x20000000 +}; + static bool is_unsigned_opcode(Op op) { // Don't have to be exhaustive, only relevant for legacy target checking ... @@ -3601,6 +3608,21 @@ void CompilerGLSL::emit_output_variable_initializer(const SPIRVariable &var) statement(to_expression(var.self), "[gl_InvocationID] = ", lut_name, "[gl_InvocationID];"); }); } + else if (has_decoration(var.self, DecorationBuiltIn) && + BuiltIn(get_decoration(var.self, DecorationBuiltIn)) == BuiltInSampleMask) + { + // We cannot copy the array since gl_SampleMask is unsized in GLSL. Unroll time! <_< + entry_func.fixup_hooks_in.push_back([&] { + auto &c = this->get(var.initializer); + uint32_t num_constants = uint32_t(c.subconstants.size()); + for (uint32_t i = 0; i < num_constants; i++) + { + // Don't use to_expression on constant since it might be uint, just fish out the raw int. + statement(to_expression(var.self), "[", i, "] = ", + convert_to_string(this->get(c.subconstants[i]).scalar_i32()), ";"); + } + }); + } else { auto lut_name = join("_", var.self, "_init"); @@ -5700,14 +5722,27 @@ void CompilerGLSL::emit_unary_func_op_cast(uint32_t result_type, uint32_t result // Bit-widths might be different in unary cases because we use it for SConvert/UConvert and friends. expected_type.basetype = input_type; expected_type.width = expr_type.width; - string cast_op = expr_type.basetype != input_type ? bitcast_glsl(expected_type, op0) : to_unpacked_expression(op0); + + string cast_op; + if (expr_type.basetype != input_type) + { + if (expr_type.basetype == SPIRType::Boolean) + cast_op = join(type_to_glsl(expected_type), "(", to_unpacked_expression(op0), ")"); + else + cast_op = bitcast_glsl(expected_type, op0); + } + else + cast_op = to_unpacked_expression(op0); string expr; if (out_type.basetype != expected_result_type) { expected_type.basetype = expected_result_type; expected_type.width = out_type.width; - expr = bitcast_glsl_op(out_type, expected_type); + if (out_type.basetype == SPIRType::Boolean) + expr = type_to_glsl(out_type); + else + expr = bitcast_glsl_op(out_type, expected_type); expr += '('; expr += join(op, "(", cast_op, ")"); expr += ')'; @@ -7364,7 +7399,7 @@ void CompilerGLSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, { // Make sure we have a unique ID here to avoid aliasing the extra sub-expressions between clamp and NMin sub-op. // IDs cannot exceed 24 bits, so we can make use of the higher bits for some unique flags. - uint32_t &max_id = extra_sub_expressions[id | 0x80000000u]; + uint32_t &max_id = extra_sub_expressions[id | EXTRA_SUB_EXPRESSION_TYPE_AUX]; if (!max_id) max_id = ir.increase_bound_by(1); @@ -7646,6 +7681,9 @@ void CompilerGLSL::emit_subgroup_op(const Instruction &i) case OpGroupNonUniformBitwiseAnd: case OpGroupNonUniformBitwiseOr: case OpGroupNonUniformBitwiseXor: + case OpGroupNonUniformLogicalAnd: + case OpGroupNonUniformLogicalOr: + case OpGroupNonUniformLogicalXor: { auto operation = static_cast(ops[3]); if (operation == GroupOperationClusteredReduce) @@ -7802,6 +7840,9 @@ case OpGroupNonUniform##op: \ GLSL_GROUP_OP(BitwiseAnd, And) GLSL_GROUP_OP(BitwiseOr, Or) GLSL_GROUP_OP(BitwiseXor, Xor) + GLSL_GROUP_OP(LogicalAnd, And) + GLSL_GROUP_OP(LogicalOr, Or) + GLSL_GROUP_OP(LogicalXor, Xor) #undef GLSL_GROUP_OP #undef GLSL_GROUP_OP_CAST // clang-format on @@ -9638,17 +9679,20 @@ void CompilerGLSL::emit_store_statement(uint32_t lhs_expression, uint32_t rhs_ex { handle_store_to_invariant_variable(lhs_expression, rhs_expression); - auto lhs = to_dereferenced_expression(lhs_expression); + if (!unroll_array_to_complex_store(lhs_expression, rhs_expression)) + { + auto lhs = to_dereferenced_expression(lhs_expression); - // We might need to cast in order to store to a builtin. - cast_to_builtin_store(lhs_expression, rhs, expression_type(rhs_expression)); + // We might need to cast in order to store to a builtin. + cast_to_builtin_store(lhs_expression, rhs, expression_type(rhs_expression)); - // Tries to optimize assignments like " = op expr". - // While this is purely cosmetic, this is important for legacy ESSL where loop - // variable increments must be in either i++ or i += const-expr. - // Without this, we end up with i = i + 1, which is correct GLSL, but not correct GLES 2.0. - if (!optimize_read_modify_write(expression_type(rhs_expression), lhs, rhs)) - statement(lhs, " = ", rhs, ";"); + // Tries to optimize assignments like " = op expr". + // While this is purely cosmetic, this is important for legacy ESSL where loop + // variable increments must be in either i++ or i += const-expr. + // Without this, we end up with i = i + 1, which is correct GLSL, but not correct GLES 2.0. + if (!optimize_read_modify_write(expression_type(rhs_expression), lhs, rhs)) + statement(lhs, " = ", rhs, ";"); + } register_write(lhs_expression); } } @@ -9810,6 +9854,7 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) // We might be trying to load a gl_Position[N], where we should be // doing float4[](gl_in[i].gl_Position, ...) instead. // Similar workarounds are required for input arrays in tessellation. + // Also, loading from gl_SampleMask array needs special unroll. unroll_array_from_complex_load(id, ptr, expr); // Shouldn't need to check for ID, but current glslang codegen requires it in some cases @@ -10314,10 +10359,27 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) uint32_t rhs = ops[1]; if (lhs != rhs) { - flush_variable_declaration(lhs); - flush_variable_declaration(rhs); - statement(to_expression(lhs), " = ", to_unpacked_expression(rhs), ";"); - register_write(lhs); + uint32_t &tmp_id = extra_sub_expressions[instruction.offset | EXTRA_SUB_EXPRESSION_TYPE_STREAM_OFFSET]; + if (!tmp_id) + tmp_id = ir.increase_bound_by(1); + uint32_t tmp_type_id = expression_type(rhs).parent_type; + + EmbeddedInstruction fake_load, fake_store; + fake_load.op = OpLoad; + fake_load.length = 3; + fake_load.ops.push_back(tmp_type_id); + fake_load.ops.push_back(tmp_id); + fake_load.ops.push_back(rhs); + + fake_store.op = OpStore; + fake_store.length = 2; + fake_store.ops.push_back(lhs); + fake_store.ops.push_back(tmp_id); + + // Load and Store do a *lot* of workarounds, and we'd like to reuse them as much as possible. + // Synthesize a fake Load and Store pair for CopyMemory. + emit_instruction(fake_load); + emit_instruction(fake_store); } break; } @@ -12148,6 +12210,9 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) case OpGroupNonUniformBitwiseAnd: case OpGroupNonUniformBitwiseOr: case OpGroupNonUniformBitwiseXor: + case OpGroupNonUniformLogicalAnd: + case OpGroupNonUniformLogicalOr: + case OpGroupNonUniformLogicalXor: case OpGroupNonUniformQuadSwap: case OpGroupNonUniformQuadBroadcast: emit_subgroup_op(instruction); @@ -12791,7 +12856,7 @@ string CompilerGLSL::variable_decl(const SPIRVariable &variable) // Ignore the pointer type since GLSL doesn't have pointers. auto &type = get_variable_data_type(variable); - if (type.pointer_depth > 1) + if (type.pointer_depth > 1 && !backend.support_pointer_to_pointer) SPIRV_CROSS_THROW("Cannot declare pointer-to-pointer types."); auto res = join(to_qualifiers_glsl(variable.self), variable_decl(type, to_name(variable.self), variable.self)); @@ -14920,6 +14985,43 @@ void CompilerGLSL::emit_array_copy(const string &lhs, uint32_t rhs_id, StorageCl statement(lhs, " = ", to_expression(rhs_id), ";"); } +bool CompilerGLSL::unroll_array_to_complex_store(uint32_t target_id, uint32_t source_id) +{ + if (!backend.force_gl_in_out_block) + return false; + // This path is only relevant for GL backends. + + auto *var = maybe_get(target_id); + if (!var || var->storage != StorageClassOutput) + return false; + + if (!is_builtin_variable(*var) || BuiltIn(get_decoration(var->self, DecorationBuiltIn)) != BuiltInSampleMask) + return false; + + auto &type = expression_type(source_id); + string array_expr; + if (type.array_size_literal.back()) + { + array_expr = convert_to_string(type.array.back()); + if (type.array.back() == 0) + SPIRV_CROSS_THROW("Cannot unroll an array copy from unsized array."); + } + else + array_expr = to_expression(type.array.back()); + + SPIRType target_type; + target_type.basetype = SPIRType::Int; + + statement("for (int i = 0; i < int(", array_expr, "); i++)"); + begin_scope(); + statement(to_expression(target_id), "[i] = ", + bitcast_expression(target_type, type.basetype, join(to_expression(source_id), "[i]")), + ";"); + end_scope(); + + return true; +} + void CompilerGLSL::unroll_array_from_complex_load(uint32_t target_id, uint32_t source_id, std::string &expr) { if (!backend.force_gl_in_out_block) @@ -14930,7 +15032,7 @@ void CompilerGLSL::unroll_array_from_complex_load(uint32_t target_id, uint32_t s if (!var) return; - if (var->storage != StorageClassInput) + if (var->storage != StorageClassInput && var->storage != StorageClassOutput) return; auto &type = get_variable_data_type(*var); @@ -14938,9 +15040,13 @@ void CompilerGLSL::unroll_array_from_complex_load(uint32_t target_id, uint32_t s return; auto builtin = BuiltIn(get_decoration(var->self, DecorationBuiltIn)); - bool is_builtin = is_builtin_variable(*var) && (builtin == BuiltInPointSize || builtin == BuiltInPosition); + bool is_builtin = is_builtin_variable(*var) && + (builtin == BuiltInPointSize || + builtin == BuiltInPosition || + builtin == BuiltInSampleMask); bool is_tess = is_tessellation_shader(); bool is_patch = has_decoration(var->self, DecorationPatch); + bool is_sample_mask = is_builtin && builtin == BuiltInSampleMask; // Tessellation input arrays are special in that they are unsized, so we cannot directly copy from it. // We must unroll the array load. @@ -14964,8 +15070,14 @@ void CompilerGLSL::unroll_array_from_complex_load(uint32_t target_id, uint32_t s // The array size might be a specialization constant, so use a for-loop instead. statement("for (int i = 0; i < int(", array_expr, "); i++)"); begin_scope(); - if (is_builtin) + if (is_builtin && !is_sample_mask) statement(new_expr, "[i] = gl_in[i].", expr, ";"); + else if (is_sample_mask) + { + SPIRType target_type; + target_type.basetype = SPIRType::Int; + statement(new_expr, "[i] = ", bitcast_expression(target_type, type.basetype, join(expr, "[i]")), ";"); + } else statement(new_expr, "[i] = ", expr, "[i];"); end_scope(); @@ -14976,6 +15088,10 @@ void CompilerGLSL::unroll_array_from_complex_load(uint32_t target_id, uint32_t s void CompilerGLSL::cast_from_builtin_load(uint32_t source_id, std::string &expr, const SPIRType &expr_type) { + // We will handle array cases elsewhere. + if (!expr_type.array.empty()) + return; + auto *var = maybe_get_backing_variable(source_id); if (var) source_id = var->self; @@ -15003,6 +15119,7 @@ void CompilerGLSL::cast_from_builtin_load(uint32_t source_id, std::string &expr, case BuiltInDrawIndex: case BuiltInFragStencilRefEXT: case BuiltInInstanceCustomIndexNV: + case BuiltInSampleMask: expected_type = SPIRType::Int; break; @@ -15028,6 +15145,10 @@ void CompilerGLSL::cast_from_builtin_load(uint32_t source_id, std::string &expr, void CompilerGLSL::cast_to_builtin_store(uint32_t target_id, std::string &expr, const SPIRType &expr_type) { + auto *var = maybe_get_backing_variable(target_id); + if (var) + target_id = var->self; + // Only interested in standalone builtin variables. if (!has_decoration(target_id, DecorationBuiltIn)) return; @@ -15042,6 +15163,7 @@ void CompilerGLSL::cast_to_builtin_store(uint32_t target_id, std::string &expr, case BuiltInPrimitiveId: case BuiltInViewportIndex: case BuiltInFragStencilRefEXT: + case BuiltInSampleMask: expected_type = SPIRType::Int; break; diff --git a/3rdparty/spirv-cross/spirv_glsl.hpp b/3rdparty/spirv-cross/spirv_glsl.hpp index 00b081973..0e0303824 100644 --- a/3rdparty/spirv-cross/spirv_glsl.hpp +++ b/3rdparty/spirv-cross/spirv_glsl.hpp @@ -568,6 +568,7 @@ protected: bool support_case_fallthrough = true; bool use_array_constructor = false; bool needs_row_major_load_workaround = false; + bool support_pointer_to_pointer = false; } backend; void emit_struct(SPIRType &type); @@ -879,6 +880,7 @@ protected: virtual void cast_to_builtin_store(uint32_t target_id, std::string &expr, const SPIRType &expr_type); virtual void cast_from_builtin_load(uint32_t source_id, std::string &expr, const SPIRType &expr_type); void unroll_array_from_complex_load(uint32_t target_id, uint32_t source_id, std::string &expr); + bool unroll_array_to_complex_store(uint32_t target_id, uint32_t source_id); void convert_non_uniform_expression(const SPIRType &type, std::string &expr); void handle_store_to_invariant_variable(uint32_t store_id, uint32_t value_id); diff --git a/3rdparty/spirv-cross/spirv_hlsl.cpp b/3rdparty/spirv-cross/spirv_hlsl.cpp index 04e380e59..c0b6d1d9a 100644 --- a/3rdparty/spirv-cross/spirv_hlsl.cpp +++ b/3rdparty/spirv-cross/spirv_hlsl.cpp @@ -4635,12 +4635,8 @@ void CompilerHLSL::emit_subgroup_op(const Instruction &i) break; case OpGroupNonUniformAllEqual: - { - auto &type = get(result_type); - emit_unary_func_op(result_type, id, ops[3], - type.basetype == SPIRType::Boolean ? "WaveActiveAllEqualBool" : "WaveActiveAllEqual"); + emit_unary_func_op(result_type, id, ops[3], "WaveActiveAllEqual"); break; - } // clang-format off #define HLSL_GROUP_OP(op, hlsl_op, supports_scan) \ @@ -4688,6 +4684,9 @@ case OpGroupNonUniform##op: \ HLSL_GROUP_OP(BitwiseAnd, BitAnd, false) HLSL_GROUP_OP(BitwiseOr, BitOr, false) HLSL_GROUP_OP(BitwiseXor, BitXor, false) + HLSL_GROUP_OP_CAST(LogicalAnd, BitAnd, uint_type) + HLSL_GROUP_OP_CAST(LogicalOr, BitOr, uint_type) + HLSL_GROUP_OP_CAST(LogicalXor, BitXor, uint_type) #undef HLSL_GROUP_OP #undef HLSL_GROUP_OP_CAST diff --git a/3rdparty/spirv-cross/spirv_msl.cpp b/3rdparty/spirv-cross/spirv_msl.cpp index e72328a8f..1d19e6df3 100644 --- a/3rdparty/spirv-cross/spirv_msl.cpp +++ b/3rdparty/spirv-cross/spirv_msl.cpp @@ -100,7 +100,16 @@ void CompilerMSL::set_argument_buffer_device_address_space(uint32_t desc_set, bo bool CompilerMSL::is_msl_shader_input_used(uint32_t location) { - return inputs_in_use.count(location) != 0; + return location_inputs_in_use.count(location) != 0; +} + +uint32_t CompilerMSL::get_automatic_builtin_input_location(spv::BuiltIn builtin) const +{ + auto itr = builtin_to_automatic_input_location.find(builtin); + if (itr == builtin_to_automatic_input_location.end()) + return k_unknown_location; + else + return itr->second; } bool CompilerMSL::is_msl_resource_binding_used(ExecutionModel model, uint32_t desc_set, uint32_t binding) const @@ -390,6 +399,7 @@ void CompilerMSL::build_implicit_builtins() SPIRType vec4_type_ptr; vec4_type_ptr = vec4_type; vec4_type_ptr.pointer = true; + vec4_type_ptr.pointer_depth++; vec4_type_ptr.parent_type = type_id; vec4_type_ptr.storage = StorageClassInput; auto &ptr_type = set(type_ptr_id, vec4_type_ptr); @@ -411,6 +421,7 @@ void CompilerMSL::build_implicit_builtins() SPIRType uint_type_ptr; uint_type_ptr = get_uint_type(); uint_type_ptr.pointer = true; + uint_type_ptr.pointer_depth++; uint_type_ptr.parent_type = get_uint_type_id(); uint_type_ptr.storage = StorageClassInput; auto &ptr_type = set(type_ptr_id, uint_type_ptr); @@ -432,6 +443,7 @@ void CompilerMSL::build_implicit_builtins() SPIRType uint_type_ptr; uint_type_ptr = get_uint_type(); uint_type_ptr.pointer = true; + uint_type_ptr.pointer_depth++; uint_type_ptr.parent_type = get_uint_type_id(); uint_type_ptr.storage = StorageClassInput; auto &ptr_type = set(type_ptr_id, uint_type_ptr); @@ -454,6 +466,7 @@ void CompilerMSL::build_implicit_builtins() SPIRType uint_type_ptr; uint_type_ptr = get_uint_type(); uint_type_ptr.pointer = true; + uint_type_ptr.pointer_depth++; uint_type_ptr.parent_type = get_uint_type_id(); uint_type_ptr.storage = StorageClassInput; auto &ptr_type = set(type_ptr_id, uint_type_ptr); @@ -473,6 +486,7 @@ void CompilerMSL::build_implicit_builtins() SPIRType uint_type_ptr; uint_type_ptr = get_uint_type(); uint_type_ptr.pointer = true; + uint_type_ptr.pointer_depth++; uint_type_ptr.parent_type = get_uint_type_id(); uint_type_ptr.storage = StorageClassInput; auto &ptr_type = set(type_ptr_id, uint_type_ptr); @@ -532,6 +546,7 @@ void CompilerMSL::build_implicit_builtins() SPIRType uint_type_ptr_out; uint_type_ptr_out = get_uint_type(); uint_type_ptr_out.pointer = true; + uint_type_ptr_out.pointer_depth++; uint_type_ptr_out.parent_type = get_uint_type_id(); uint_type_ptr_out.storage = StorageClassOutput; auto &ptr_out_type = set(type_ptr_out_id, uint_type_ptr_out); @@ -563,6 +578,7 @@ void CompilerMSL::build_implicit_builtins() SPIRType uint_type_ptr; uint_type_ptr = get_uint_type(); uint_type_ptr.pointer = true; + uint_type_ptr.pointer_depth++; uint_type_ptr.parent_type = get_uint_type_id(); uint_type_ptr.storage = StorageClassInput; auto &ptr_type = set(type_ptr_id, uint_type_ptr); @@ -622,6 +638,7 @@ void CompilerMSL::build_implicit_builtins() SPIRType uint_type_ptr; uint_type_ptr = get_uint_type(); uint_type_ptr.pointer = true; + uint_type_ptr.pointer_depth++; uint_type_ptr.parent_type = get_uint_type_id(); uint_type_ptr.storage = StorageClassInput; auto &ptr_type = set(type_ptr_id, uint_type_ptr); @@ -643,6 +660,7 @@ void CompilerMSL::build_implicit_builtins() SPIRType uint_type_ptr; uint_type_ptr = get_uint_type(); uint_type_ptr.pointer = true; + uint_type_ptr.pointer_depth++; uint_type_ptr.parent_type = get_uint_type_id(); uint_type_ptr.storage = StorageClassInput; auto &ptr_type = set(type_ptr_id, uint_type_ptr); @@ -701,6 +719,7 @@ void CompilerMSL::build_implicit_builtins() SPIRType uint_type_ptr_out; uint_type_ptr_out = get_uint_type(); uint_type_ptr_out.pointer = true; + uint_type_ptr_out.pointer_depth++; uint_type_ptr_out.parent_type = get_uint_type_id(); uint_type_ptr_out.storage = StorageClassOutput; @@ -722,6 +741,7 @@ void CompilerMSL::build_implicit_builtins() SPIRType uint_type_ptr; uint_type_ptr = get_uint_type(); uint_type_ptr.pointer = true; + uint_type_ptr.pointer_depth++; uint_type_ptr.parent_type = get_uint_type_id(); uint_type_ptr.storage = StorageClassInput; @@ -743,6 +763,7 @@ void CompilerMSL::build_implicit_builtins() uint32_t type_id = build_extended_vector_type(get_uint_type_id(), 3); SPIRType uint_type_ptr = get(type_id); uint_type_ptr.pointer = true; + uint_type_ptr.pointer_depth++; uint_type_ptr.parent_type = type_id; uint_type_ptr.storage = StorageClassInput; @@ -835,6 +856,7 @@ void CompilerMSL::build_implicit_builtins() SPIRType vec4_type_ptr; vec4_type_ptr = vec4_type; vec4_type_ptr.pointer = true; + vec4_type_ptr.pointer_depth++; vec4_type_ptr.parent_type = type_id; vec4_type_ptr.storage = StorageClassOutput; auto &ptr_type = set(type_ptr_id, vec4_type_ptr); @@ -910,7 +932,7 @@ uint32_t CompilerMSL::build_constant_uint_array_pointer() // Create a buffer to hold extra data, including the swizzle constants. SPIRType uint_type_pointer = get_uint_type(); uint_type_pointer.pointer = true; - uint_type_pointer.pointer_depth = 1; + uint_type_pointer.pointer_depth++; uint_type_pointer.parent_type = get_uint_type_id(); uint_type_pointer.storage = StorageClassUniform; set(type_ptr_id, uint_type_pointer); @@ -1269,6 +1291,7 @@ string CompilerMSL::compile() backend.array_is_value_type = !msl_options.force_native_arrays; // Arrays which are part of buffer objects are never considered to be native arrays. backend.buffer_offset_array_is_value_type = false; + backend.support_pointer_to_pointer = true; capture_output_to_buffer = msl_options.capture_output_to_buffer; is_rasterization_disabled = msl_options.disable_rasterization || capture_output_to_buffer; @@ -1730,6 +1753,7 @@ void CompilerMSL::extract_global_variables_from_function(uint32_t func_id, std:: ptr.self = mbr_type_id; ptr.storage = var.storage; ptr.pointer = true; + ptr.pointer_depth++; ptr.parent_type = mbr_type_id; func.add_parameter(mbr_type_id, var_id, true); @@ -1801,34 +1825,28 @@ void CompilerMSL::mark_as_packable(SPIRType &type) } } +uint32_t CompilerMSL::type_to_location_count(const SPIRType &type) const +{ + // In MSL, we cannot place structs in any context where we need locations. + assert(type.basetype != SPIRType::Struct); + + uint32_t dim = 1; + for (uint32_t i = 0; i < type.array.size(); i++) + dim *= to_array_size_literal(type, i); + + uint32_t count = dim * type.columns; + return count; +} + // If a shader input exists at the location, it is marked as being used by this shader void CompilerMSL::mark_location_as_used_by_shader(uint32_t location, const SPIRType &type, StorageClass storage) { if (storage != StorageClassInput) return; - if (is_array(type)) - { - uint32_t dim = 1; - for (uint32_t i = 0; i < type.array.size(); i++) - dim *= to_array_size_literal(type, i); - for (uint32_t i = 0; i < dim; i++) - { - if (is_matrix(type)) - { - for (uint32_t j = 0; j < type.columns; j++) - inputs_in_use.insert(location++); - } - else - inputs_in_use.insert(location++); - } - } - else if (is_matrix(type)) - { - for (uint32_t i = 0; i < type.columns; i++) - inputs_in_use.insert(location + i); - } - else - inputs_in_use.insert(location); + + uint32_t count = type_to_location_count(type); + for (uint32_t i = 0; i < count; i++) + location_inputs_in_use.insert(location + i); } uint32_t CompilerMSL::get_target_components_for_fragment_location(uint32_t location) const @@ -1872,6 +1890,7 @@ uint32_t CompilerMSL::build_extended_vector_type(uint32_t type_id, uint32_t comp type->parent_type = new_type_id; type->storage = old_type.storage; type->pointer = true; + type->pointer_depth++; new_type_id = ptr_type_id; } @@ -2726,7 +2745,7 @@ void CompilerMSL::add_tess_level_input_to_interface_block(const std::string &ib_ // Force the variable to have the proper name. set_name(var.self, builtin_to_glsl(builtin, StorageClassFunction)); - if (get_entry_point().flags.get(ExecutionModeTriangles)) + if (get_execution_mode_bitset().get(ExecutionModeTriangles)) { // Triangles are tricky, because we want only one member in the struct. @@ -2749,6 +2768,10 @@ void CompilerMSL::add_tess_level_input_to_interface_block(const std::string &ib_ // Give the member a name set_member_name(ib_type.self, ib_mbr_idx, mbr_name); + // We cannot decorate both, but the important part is that + // it's marked as builtin so we can get automatic attribute assignment if needed. + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationBuiltIn, builtin); + // There is no qualified alias since we need to flatten the internal array on return. if (get_decoration_bitset(var.self).get(DecorationLocation)) { @@ -2796,6 +2819,7 @@ void CompilerMSL::add_tess_level_input_to_interface_block(const std::string &ib_ uint32_t ptr_type_id = ir.increase_bound_by(1); auto &new_var_type = set(ptr_type_id, get(type_id)); new_var_type.pointer = true; + new_var_type.pointer_depth++; new_var_type.storage = StorageClassInput; new_var_type.parent_type = type_id; var.basetype = ptr_type_id; @@ -2811,6 +2835,8 @@ void CompilerMSL::add_tess_level_input_to_interface_block(const std::string &ib_ string qual_var_name = ib_var_ref + "." + mbr_name; ir.meta[var.self].decoration.qualified_alias = qual_var_name; + set_member_decoration(ib_type.self, ib_mbr_idx, DecorationBuiltIn, builtin); + if (get_decoration_bitset(var.self).get(DecorationLocation)) { uint32_t locn = get_decoration(var.self, DecorationLocation); @@ -3301,6 +3327,7 @@ uint32_t CompilerMSL::add_interface_block(StorageClass storage, bool patch) set(array_type_id, type); type.pointer = true; + type.pointer_depth++; type.parent_type = array_type_id; type.storage = storage; auto &ptr_type = set(ptr_type_id, type); @@ -3356,6 +3383,7 @@ uint32_t CompilerMSL::add_interface_block_pointer(uint32_t ib_var_id, StorageCla auto &ib_ptr_type = set(ib_ptr_type_id, ib_type); ib_ptr_type.parent_type = ib_ptr_type.type_alias = ib_type.self; ib_ptr_type.pointer = true; + ib_ptr_type.pointer_depth++; ib_ptr_type.storage = storage == StorageClassInput ? (msl_options.multi_patch_workgroup ? StorageClassStorageBuffer : StorageClassWorkgroup) : @@ -3421,6 +3449,7 @@ uint32_t CompilerMSL::ensure_correct_builtin_type(uint32_t type_id, BuiltIn buil auto &ptr_type = set(ptr_type_id); ptr_type = base_type; ptr_type.pointer = true; + ptr_type.pointer_depth++; ptr_type.storage = type.storage; ptr_type.parent_type = base_type_id; return ptr_type_id; @@ -6088,6 +6117,30 @@ void CompilerMSL::emit_custom_functions() } } +static string inject_top_level_storage_qualifier(const string &expr, const string &qualifier) +{ + // Easier to do this through text munging since the qualifier does not exist in the type system at all, + // and plumbing in all that information is not very helpful. + size_t last_reference = expr.find_last_of('&'); + size_t last_pointer = expr.find_last_of('*'); + size_t last_significant = string::npos; + + if (last_reference == string::npos) + last_significant = last_pointer; + else if (last_pointer == string::npos) + last_significant = last_reference; + else + last_significant = std::max(last_reference, last_pointer); + + if (last_significant == string::npos) + return join(qualifier, " ", expr); + else + { + return join(expr.substr(0, last_significant + 1), " ", + qualifier, expr.substr(last_significant + 1, string::npos)); + } +} + // Undefined global memory is not allowed in MSL. // Declare constant and init to zeros. Use {}, as global constructors can break Metal. void CompilerMSL::declare_undefined_values() @@ -6099,7 +6152,10 @@ void CompilerMSL::declare_undefined_values() if (type.basetype == SPIRType::Void) return; - statement("constant ", variable_decl(type, to_name(undef.self), undef.self), " = {};"); + statement(inject_top_level_storage_qualifier( + variable_decl(type, to_name(undef.self), undef.self), + "constant"), + " = {};"); emitted = true; }); @@ -6127,7 +6183,8 @@ void CompilerMSL::declare_constant_arrays() if (!type.array.empty() && (!fully_inlined || is_scalar(type) || is_vector(type))) { auto name = to_name(c.self); - statement("constant ", variable_decl(type, name), " = ", constant_expression(c), ";"); + statement(inject_top_level_storage_qualifier(variable_decl(type, name), "constant"), + " = ", constant_expression(c), ";"); emitted = true; } }); @@ -7997,6 +8054,7 @@ void CompilerMSL::emit_array_copy(const string &lhs, uint32_t rhs_id, StorageCla bool lhs_thread = (lhs_storage == StorageClassOutput || lhs_storage == StorageClassFunction || lhs_storage == StorageClassGeneric || lhs_storage == StorageClassPrivate); bool rhs_thread = (rhs_storage == StorageClassInput || rhs_storage == StorageClassFunction || + rhs_storage == StorageClassOutput || rhs_storage == StorageClassGeneric || rhs_storage == StorageClassPrivate); // If threadgroup storage qualifiers are *not* used: @@ -8080,6 +8138,14 @@ void CompilerMSL::emit_array_copy(const string &lhs, uint32_t rhs_id, StorageCla } } +uint32_t CompilerMSL::get_physical_tess_level_array_size(spv::BuiltIn builtin) const +{ + if (get_execution_mode_bitset().get(ExecutionModeTriangles)) + return builtin == BuiltInTessLevelInner ? 1 : 3; + else + return builtin == BuiltInTessLevelInner ? 2 : 4; +} + // Since MSL does not allow arrays to be copied via simple variable assignment, // if the LHS and RHS represent an assignment of an entire array, it must be // implemented by calling an array copy function. @@ -8108,6 +8174,25 @@ bool CompilerMSL::maybe_emit_array_assignment(uint32_t id_lhs, uint32_t id_rhs) return true; } + if (get_execution_model() == ExecutionModelTessellationControl && + has_decoration(id_lhs, DecorationBuiltIn)) + { + auto builtin = BuiltIn(get_decoration(id_lhs, DecorationBuiltIn)); + // Need to manually unroll the array store. + if (builtin == BuiltInTessLevelInner || builtin == BuiltInTessLevelOuter) + { + uint32_t array_size = get_physical_tess_level_array_size(builtin); + if (array_size == 1) + statement(to_expression(id_lhs), " = half(", to_expression(id_rhs), "[0]);"); + else + { + for (uint32_t i = 0; i < array_size; i++) + statement(to_expression(id_lhs), "[", i, "] = half(", to_expression(id_rhs), "[", i, "]);"); + } + return true; + } + } + // Ensure the LHS variable has been declared auto *p_v_lhs = maybe_get_backing_variable(id_lhs); if (p_v_lhs) @@ -10047,7 +10132,13 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in return ""; } } - uint32_t locn = get_ordered_member_location(type.self, index); + + uint32_t locn; + if (is_builtin) + locn = get_or_allocate_builtin_input_member_location(builtin, type.self, index); + else + locn = get_member_location(type.self, index); + if (locn != k_unknown_location) return string(" [[attribute(") + convert_to_string(locn) + ")]]"; } @@ -10087,7 +10178,7 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in } } uint32_t comp; - uint32_t locn = get_ordered_member_location(type.self, index, &comp); + uint32_t locn = get_member_location(type.self, index, &comp); if (locn != k_unknown_location) { if (comp != k_unknown_component) @@ -10123,7 +10214,13 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in } if (msl_options.multi_patch_workgroup) return ""; - uint32_t locn = get_ordered_member_location(type.self, index); + + uint32_t locn; + if (is_builtin) + locn = get_or_allocate_builtin_input_member_location(builtin, type.self, index); + else + locn = get_member_location(type.self, index); + if (locn != k_unknown_location) return string(" [[attribute(") + convert_to_string(locn) + ")]]"; } @@ -10156,7 +10253,13 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in // The special control point array must not be marked with an attribute. if (get_type(type.member_types[index]).basetype == SPIRType::ControlPointArray) return ""; - uint32_t locn = get_ordered_member_location(type.self, index); + + uint32_t locn; + if (is_builtin) + locn = get_or_allocate_builtin_input_member_location(builtin, type.self, index); + else + locn = get_member_location(type.self, index); + if (locn != k_unknown_location) return string(" [[attribute(") + convert_to_string(locn) + ")]]"; } @@ -10196,7 +10299,7 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in else { uint32_t comp; - uint32_t locn = get_ordered_member_location(type.self, index, &comp); + uint32_t locn = get_member_location(type.self, index, &comp); if (locn != k_unknown_location) { // For user-defined attributes, this is fine. From Vulkan spec: @@ -10298,7 +10401,7 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in return ""; } } - uint32_t locn = get_ordered_member_location(type.self, index); + uint32_t locn = get_member_location(type.self, index); // Metal will likely complain about missing color attachments, too. if (locn != k_unknown_location && !(msl_options.enable_frag_output_mask & (1 << locn))) return ""; @@ -10347,24 +10450,61 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in // If the location of the member has been explicitly set, that location is used. If not, this // function assumes the members are ordered in their location order, and simply returns the // index as the location. -uint32_t CompilerMSL::get_ordered_member_location(uint32_t type_id, uint32_t index, uint32_t *comp) +uint32_t CompilerMSL::get_member_location(uint32_t type_id, uint32_t index, uint32_t *comp) const { - auto &m = ir.meta[type_id]; - if (index < m.members.size()) + if (comp) { - auto &dec = m.members[index]; - if (comp) - { - if (dec.decoration_flags.get(DecorationComponent)) - *comp = dec.component; - else - *comp = k_unknown_component; - } - if (dec.decoration_flags.get(DecorationLocation)) - return dec.location; + if (has_member_decoration(type_id, index, DecorationComponent)) + *comp = get_member_decoration(type_id, index, DecorationComponent); + else + *comp = k_unknown_component; } - return index; + if (has_member_decoration(type_id, index, DecorationLocation)) + return get_member_decoration(type_id, index, DecorationLocation); + else + return k_unknown_location; +} + +uint32_t CompilerMSL::get_or_allocate_builtin_input_member_location(spv::BuiltIn builtin, + uint32_t type_id, uint32_t index, + uint32_t *comp) +{ + uint32_t loc = get_member_location(type_id, index, comp); + if (loc != k_unknown_location) + return loc; + + if (comp) + *comp = k_unknown_component; + + // Late allocation. Find a location which is unused by the application. + // This can happen for built-in inputs in tessellation which are mixed and matched with user inputs. + auto &mbr_type = get(get(type_id).member_types[index]); + uint32_t count = type_to_location_count(mbr_type); + + // This should always be 1. + if (count != 1) + return k_unknown_location; + + loc = 0; + while (location_inputs_in_use.count(loc) != 0) + loc++; + + set_member_decoration(type_id, index, DecorationLocation, loc); + + // Triangle tess level inputs are shared in one packed float4, + // mark both builtins as sharing one location. + if (get_execution_mode_bitset().get(ExecutionModeTriangles) && + (builtin == BuiltInTessLevelInner || builtin == BuiltInTessLevelOuter)) + { + builtin_to_automatic_input_location[BuiltInTessLevelInner] = loc; + builtin_to_automatic_input_location[BuiltInTessLevelOuter] = loc; + } + else + builtin_to_automatic_input_location[builtin] = loc; + + mark_location_as_used_by_shader(loc, mbr_type, StorageClassInput); + return loc; } // Returns the type declaration for a function, including the @@ -10508,8 +10648,10 @@ string CompilerMSL::get_type_address_space(const SPIRType &type, uint32_t id, bo } if (!addr_space) + { // No address space for plain values. addr_space = type.pointer || (argument && type.basetype == SPIRType::ControlPointArray) ? "thread" : ""; + } return join(flags.get(DecorationVolatile) || flags.get(DecorationCoherent) ? "volatile " : "", addr_space); } @@ -11838,6 +11980,24 @@ bool CompilerMSL::type_is_msl_framebuffer_fetch(const SPIRType &type) const msl_options.use_framebuffer_fetch_subpasses; } +bool CompilerMSL::type_is_pointer(const SPIRType &type) const +{ + if (!type.pointer) + return false; + auto &parent_type = get(type.parent_type); + // Safeguards when we forget to set pointer_depth (there is an assert for it in type_to_glsl), + // but the extra check shouldn't hurt. + return (type.pointer_depth > parent_type.pointer_depth) || !parent_type.pointer; +} + +bool CompilerMSL::type_is_pointer_to_pointer(const SPIRType &type) const +{ + if (!type.pointer) + return false; + auto &parent_type = get(type.parent_type); + return type.pointer_depth > parent_type.pointer_depth && type_is_pointer(parent_type); +} + string CompilerMSL::argument_decl(const SPIRFunction::Parameter &arg) { auto &var = get(arg.id); @@ -11864,9 +12024,8 @@ string CompilerMSL::argument_decl(const SPIRFunction::Parameter &arg) if (!type.array.empty() && type_is_image) constref = true; + const char *cv_qualifier = constref ? "const " : ""; string decl; - if (constref) - decl += "const "; // If this is a combined image-sampler for a 2D image with floating-point type, // we emitted the 'spvDynamicImageSampler' type, and this is *not* an alias parameter @@ -11880,27 +12039,38 @@ string CompilerMSL::argument_decl(const SPIRFunction::Parameter &arg) // Allow Metal to use the array template to make arrays a value type string address_space = get_argument_address_space(var); bool builtin = is_builtin_variable(var); + auto builtin_type = BuiltIn(get_decoration(arg.id, DecorationBuiltIn)); is_using_builtin_array = builtin; if (address_space == "threadgroup") is_using_builtin_array = true; if (var.basevariable && (var.basevariable == stage_in_ptr_var_id || var.basevariable == stage_out_ptr_var_id)) - decl += type_to_glsl(type, arg.id); + decl = join(cv_qualifier, type_to_glsl(type, arg.id)); else if (builtin) - decl += builtin_type_decl(static_cast(get_decoration(arg.id, DecorationBuiltIn)), arg.id); + decl = join(cv_qualifier, builtin_type_decl(builtin_type, arg.id)); else if ((storage == StorageClassUniform || storage == StorageClassStorageBuffer) && is_array(type)) { is_using_builtin_array = true; - decl += join(type_to_glsl(type, arg.id), "*"); + decl += join(cv_qualifier, type_to_glsl(type, arg.id), "*"); } else if (is_dynamic_img_sampler) { - decl += join("spvDynamicImageSampler<", type_to_glsl(get(type.image.type)), ">"); + decl = join(cv_qualifier, "spvDynamicImageSampler<", type_to_glsl(get(type.image.type)), ">"); // Mark the variable so that we can handle passing it to another function. set_extended_decoration(arg.id, SPIRVCrossDecorationDynamicImageSampler); } else - decl += type_to_glsl(type, arg.id); + { + // The type is a pointer type we need to emit cv_qualifier late. + if (type_is_pointer(type)) + { + decl = type_to_glsl(type, arg.id); + if (*cv_qualifier != '\0') + decl += join(" ", cv_qualifier); + } + else + decl = join(cv_qualifier, type_to_glsl(type, arg.id)); + } bool opaque_handle = storage == StorageClassUniformConstant; @@ -11968,16 +12138,37 @@ string CompilerMSL::argument_decl(const SPIRFunction::Parameter &arg) } } - decl += " (&"; - const char *restrict_kw = to_restrict(name_id); - if (*restrict_kw) + // Special case, need to override the array size here if we're using tess level as an argument. + if (get_execution_model() == ExecutionModelTessellationControl && builtin && + (builtin_type == BuiltInTessLevelInner || builtin_type == BuiltInTessLevelOuter)) { - decl += " "; - decl += restrict_kw; + uint32_t array_size = get_physical_tess_level_array_size(builtin_type); + if (array_size == 1) + { + decl += " &"; + decl += to_expression(name_id); + } + else + { + decl += " (&"; + decl += to_expression(name_id); + decl += ")"; + decl += join("[", array_size, "]"); + } + } + else + { + decl += " (&"; + const char *restrict_kw = to_restrict(name_id); + if (*restrict_kw) + { + decl += " "; + decl += restrict_kw; + } + decl += to_expression(name_id); + decl += ")"; + decl += type_to_array_glsl(type); } - decl += to_expression(name_id); - decl += ")"; - decl += type_to_array_glsl(type); } else if (!opaque_handle && (!pull_model_inputs.count(var.basevariable) || type.basetype == SPIRType::Struct)) { @@ -11985,8 +12176,12 @@ string CompilerMSL::argument_decl(const SPIRFunction::Parameter &arg) // for the reference has to go before the '&', but after the '*'. if (!address_space.empty()) { - if (decl.back() == '*') - decl += join(" ", address_space, " "); + if (type_is_pointer(type)) + { + if (*cv_qualifier == '\0') + decl += ' '; + decl += join(address_space, " "); + } else decl = join(address_space, " ", decl); } @@ -12434,8 +12629,22 @@ string CompilerMSL::type_to_glsl(const SPIRType &type, uint32_t id) // Pointer? if (type.pointer) { + assert(type.pointer_depth > 0); + const char *restrict_kw; - type_name = join(get_type_address_space(type, id), " ", type_to_glsl(get(type.parent_type), id)); + + auto type_address_space = get_type_address_space(type, id); + auto type_decl = type_to_glsl(get(type.parent_type), id); + + // Work around C pointer qualifier rules. If glsl_type is a pointer type as well + // we'll need to emit the address space to the right. + // We could always go this route, but it makes the code unnatural. + // Prefer emitting thread T *foo over T thread* foo since it's more readable, + // but we'll have to emit thread T * thread * T constant bar; for example. + if (type_is_pointer_to_pointer(type)) + type_name = join(type_decl, " ", type_address_space, " "); + else + type_name = join(type_address_space, " ", type_decl); switch (type.basetype) { @@ -14531,14 +14740,36 @@ void CompilerMSL::cast_from_builtin_load(uint32_t source_id, std::string &expr, if (expected_type != expr_type.basetype) { - if (expected_width != expr_type.width) + if (!expr_type.array.empty() && (builtin == BuiltInTessLevelInner || builtin == BuiltInTessLevelOuter)) { - // These are of different widths, so we cannot do a straight bitcast. - expr = join(type_to_glsl(expr_type), "(", expr, ")"); + // Triggers when loading TessLevel directly as an array. + // Need explicit padding + cast. + auto wrap_expr = join(type_to_glsl(expr_type), "({ "); + + uint32_t array_size = get_physical_tess_level_array_size(builtin); + for (uint32_t i = 0; i < array_size; i++) + { + if (array_size > 1) + wrap_expr += join("float(", expr, "[", i, "])"); + else + wrap_expr += join("float(", expr, ")"); + if (i + 1 < array_size) + wrap_expr += ", "; + } + + if (get_execution_mode_bitset().get(ExecutionModeTriangles)) + wrap_expr += ", 0.0"; + + wrap_expr += " })"; + expr = std::move(wrap_expr); } else { - expr = bitcast_expression(expr_type, expected_type, expr); + // These are of different widths, so we cannot do a straight bitcast. + if (expected_width != expr_type.width) + expr = join(type_to_glsl(expr_type), "(", expr, ")"); + else + expr = bitcast_expression(expr_type, expected_type, expr); } } @@ -14776,7 +15007,7 @@ void CompilerMSL::analyze_argument_buffers() // Create a buffer to hold extra data, including the swizzle constants. SPIRType uint_type_pointer = get_uint_type(); uint_type_pointer.pointer = true; - uint_type_pointer.pointer_depth = 1; + uint_type_pointer.pointer_depth++; uint_type_pointer.parent_type = get_uint_type_id(); uint_type_pointer.storage = StorageClassUniform; set(uint_ptr_type_id, uint_type_pointer); @@ -14850,7 +15081,7 @@ void CompilerMSL::analyze_argument_buffers() auto &ptr_type = set(ptr_type_id); ptr_type = buffer_type; ptr_type.pointer = true; - ptr_type.pointer_depth = 1; + ptr_type.pointer_depth++; ptr_type.parent_type = type_id; uint32_t buffer_variable_id = next_id; @@ -14937,6 +15168,7 @@ void CompilerMSL::analyze_argument_buffers() set(atomic_type_id, atomic_type); atomic_type.pointer = true; + atomic_type.pointer_depth++; atomic_type.parent_type = atomic_type_id; atomic_type.storage = StorageClassStorageBuffer; auto &atomic_ptr_type = set(type_ptr_id, atomic_type); diff --git a/3rdparty/spirv-cross/spirv_msl.hpp b/3rdparty/spirv-cross/spirv_msl.hpp index 0cce958d2..52e96761e 100644 --- a/3rdparty/spirv-cross/spirv_msl.hpp +++ b/3rdparty/spirv-cross/spirv_msl.hpp @@ -550,6 +550,13 @@ public: // Query after compilation is done. This allows you to check if an input location was used by the shader. bool is_msl_shader_input_used(uint32_t location); + // If not using add_msl_shader_input, it's possible + // that certain builtin attributes need to be automatically assigned locations. + // This is typical for tessellation builtin inputs such as tess levels, gl_Position, etc. + // This returns k_unknown_location if the location was explicitly assigned with + // add_msl_shader_input or the builtin is not used, otherwise returns N in [[attribute(N)]]. + uint32_t get_automatic_builtin_input_location(spv::BuiltIn builtin) const; + // NOTE: Only resources which are remapped using add_msl_resource_binding will be reported here. // Constexpr samplers are always assumed to be emitted. // No specific MSLResourceBinding remapping is required for constexpr samplers as long as they are remapped @@ -567,6 +574,7 @@ public: // Same as get_automatic_msl_resource_binding, but should only be used for combined image samplers, in which case the // sampler's binding is returned instead. For any other resource type, -1 is returned. + // Secondary bindings are also used for the auxillary image atomic buffer. uint32_t get_automatic_msl_resource_binding_secondary(uint32_t id) const; // Same as get_automatic_msl_resource_binding, but should only be used for combined image samplers for multiplanar images, @@ -825,7 +833,11 @@ protected: std::string argument_decl(const SPIRFunction::Parameter &arg); std::string round_fp_tex_coords(std::string tex_coords, bool coord_is_fp); uint32_t get_metal_resource_index(SPIRVariable &var, SPIRType::BaseType basetype, uint32_t plane = 0); - uint32_t get_ordered_member_location(uint32_t type_id, uint32_t index, uint32_t *comp = nullptr); + uint32_t get_member_location(uint32_t type_id, uint32_t index, uint32_t *comp = nullptr) const; + uint32_t get_or_allocate_builtin_input_member_location(spv::BuiltIn builtin, + uint32_t type_id, uint32_t index, uint32_t *comp = nullptr); + + uint32_t get_physical_tess_level_array_size(spv::BuiltIn builtin) const; // MSL packing rules. These compute the effective packing rules as observed by the MSL compiler in the MSL output. // These values can change depending on various extended decorations which control packing rules. @@ -928,13 +940,15 @@ protected: // Must be ordered to ensure declarations are in a specific order. std::map inputs_by_location; std::unordered_map inputs_by_builtin; - std::unordered_set inputs_in_use; + std::unordered_set location_inputs_in_use; std::unordered_map fragment_output_components; + std::unordered_map builtin_to_automatic_input_location; std::set pragma_lines; std::set typedef_lines; SmallVector vars_needing_early_declaration; std::unordered_map, InternalHasher> resource_bindings; + uint32_t type_to_location_count(const SPIRType &type) const; uint32_t next_metal_resource_index_buffer = 0; uint32_t next_metal_resource_index_texture = 0; @@ -1026,6 +1040,8 @@ protected: void activate_argument_buffer_resources(); bool type_is_msl_framebuffer_fetch(const SPIRType &type) const; + bool type_is_pointer(const SPIRType &type) const; + bool type_is_pointer_to_pointer(const SPIRType &type) const; bool is_supported_argument_buffer_type(const SPIRType &type) const; // OpcodeHandler that handles several MSL preprocessing operations. diff --git a/3rdparty/spirv-cross/spirv_parser.cpp b/3rdparty/spirv-cross/spirv_parser.cpp index 7159a3257..86f7fd350 100644 --- a/3rdparty/spirv-cross/spirv_parser.cpp +++ b/3rdparty/spirv-cross/spirv_parser.cpp @@ -140,6 +140,8 @@ void Parser::parse() SPIRV_CROSS_THROW("Function was not terminated."); if (current_block) SPIRV_CROSS_THROW("Block was not terminated."); + if (ir.default_entry_point == 0) + SPIRV_CROSS_THROW("There is no entry point in the SPIR-V module."); } const uint32_t *Parser::stream(const Instruction &instr) const