|
|
@@ -62,6 +62,12 @@ void CompilerMSL::add_msl_resource_binding(const MSLResourceBinding &binding)
|
|
|
resource_bindings.push_back({ binding, false });
|
|
|
}
|
|
|
|
|
|
+void CompilerMSL::add_discrete_descriptor_set(uint32_t desc_set)
|
|
|
+{
|
|
|
+ if (desc_set < kMaxArgumentBuffers)
|
|
|
+ argument_buffer_discrete_mask |= 1u << desc_set;
|
|
|
+}
|
|
|
+
|
|
|
bool CompilerMSL::is_msl_vertex_attribute_used(uint32_t location)
|
|
|
{
|
|
|
return vtx_attrs_in_use.count(location) != 0;
|
|
|
@@ -630,6 +636,15 @@ string CompilerMSL::compile()
|
|
|
// the loop, so the hooks aren't added multiple times.
|
|
|
fix_up_shader_inputs_outputs();
|
|
|
|
|
|
+ // If we are using argument buffers, we create argument buffer structures for them here.
|
|
|
+ // These buffers will be used in the entry point, not the individual resources.
|
|
|
+ if (msl_options.argument_buffers)
|
|
|
+ {
|
|
|
+ if (!msl_options.supports_msl_version(2, 0))
|
|
|
+ SPIRV_CROSS_THROW("Argument buffers can only be used with MSL 2.0 and up.");
|
|
|
+ analyze_argument_buffers();
|
|
|
+ }
|
|
|
+
|
|
|
uint32_t pass_count = 0;
|
|
|
do
|
|
|
{
|
|
|
@@ -4271,7 +4286,10 @@ void CompilerMSL::emit_function_prototype(SPIRFunction &func, const Bitset &)
|
|
|
|
|
|
if (processing_entry_point)
|
|
|
{
|
|
|
- decl += entry_point_args(!func.arguments.empty());
|
|
|
+ if (msl_options.argument_buffers)
|
|
|
+ decl += entry_point_args_argument_buffer(!func.arguments.empty());
|
|
|
+ else
|
|
|
+ decl += entry_point_args_classic(!func.arguments.empty());
|
|
|
|
|
|
// If entry point function has variables that require early declaration,
|
|
|
// ensure they each have an empty initializer, creating one if needed.
|
|
|
@@ -4778,7 +4796,15 @@ string CompilerMSL::to_func_call_arg(uint32_t id)
|
|
|
// Manufacture automatic sampler arg if the arg is a SampledImage texture.
|
|
|
auto &type = expression_type(id);
|
|
|
if (type.basetype == SPIRType::SampledImage && type.image.dim != DimBuffer)
|
|
|
- arg_str += ", " + to_sampler_expression(id);
|
|
|
+ {
|
|
|
+ // Need to check the base variable in case we need to apply a qualified alias.
|
|
|
+ uint32_t var_id = 0;
|
|
|
+ auto *sampler_var = maybe_get<SPIRVariable>(id);
|
|
|
+ if (sampler_var)
|
|
|
+ var_id = sampler_var->basevariable;
|
|
|
+
|
|
|
+ arg_str += ", " + to_sampler_expression(var_id ? var_id : id);
|
|
|
+ }
|
|
|
if (msl_options.swizzle_texture_samples && has_sampled_images && is_sampled_image_type(type))
|
|
|
arg_str += ", " + to_swizzle_expression(id);
|
|
|
|
|
|
@@ -4963,6 +4989,10 @@ string CompilerMSL::to_struct_member(const SPIRType &type, uint32_t member_type_
|
|
|
const SPIRType *effective_membertype = &membertype;
|
|
|
SPIRType override_type;
|
|
|
|
|
|
+ uint32_t orig_id = 0;
|
|
|
+ if (has_extended_member_decoration(type.self, index, SPIRVCrossDecorationInterfaceOrigID))
|
|
|
+ orig_id = get_extended_member_decoration(type.self, index, SPIRVCrossDecorationInterfaceOrigID);
|
|
|
+
|
|
|
if (member_is_packed_type(type, index))
|
|
|
{
|
|
|
// If we're packing a matrix, output an appropriate typedef
|
|
|
@@ -4992,8 +5022,23 @@ string CompilerMSL::to_struct_member(const SPIRType &type, uint32_t member_type_
|
|
|
pack_pfx = "packed_";
|
|
|
}
|
|
|
|
|
|
- return join(pack_pfx, type_to_glsl(*effective_membertype), " ", qualifier, to_member_name(type, index),
|
|
|
- member_attribute_qualifier(type, index), type_to_array_glsl(membertype), ";");
|
|
|
+ // Very specifically, image load-store in argument buffers are disallowed on MSL on iOS.
|
|
|
+ if (msl_options.is_ios() && membertype.basetype == SPIRType::Image && membertype.image.sampled == 2)
|
|
|
+ {
|
|
|
+ if (!has_decoration(orig_id, DecorationNonWritable))
|
|
|
+ SPIRV_CROSS_THROW("Writable images are not allowed in argument buffers on iOS.");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Array information is baked into these types.
|
|
|
+ string array_type;
|
|
|
+ if (membertype.basetype != SPIRType::Image && membertype.basetype != SPIRType::Sampler &&
|
|
|
+ membertype.basetype != SPIRType::SampledImage)
|
|
|
+ {
|
|
|
+ array_type = type_to_array_glsl(membertype);
|
|
|
+ }
|
|
|
+
|
|
|
+ return join(pack_pfx, type_to_glsl(*effective_membertype, orig_id), " ", qualifier, to_member_name(type, index),
|
|
|
+ member_attribute_qualifier(type, index), array_type, ";");
|
|
|
}
|
|
|
|
|
|
// Emit a structure member, padding and packing to maintain the correct memeber alignments.
|
|
|
@@ -5014,6 +5059,10 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in
|
|
|
BuiltIn builtin = BuiltInMax;
|
|
|
bool is_builtin = is_member_builtin(type, index, &builtin);
|
|
|
|
|
|
+ if (has_extended_member_decoration(type.self, index, SPIRVCrossDecorationArgumentBufferID))
|
|
|
+ return join(" [[id(", get_extended_member_decoration(type.self, index, SPIRVCrossDecorationArgumentBufferID),
|
|
|
+ ")]]");
|
|
|
+
|
|
|
// Vertex function inputs
|
|
|
if (execution.model == ExecutionModelVertex && type.storage == StorageClassInput)
|
|
|
{
|
|
|
@@ -5393,7 +5442,7 @@ string CompilerMSL::get_argument_address_space(const SPIRVariable &argument)
|
|
|
return "thread";
|
|
|
}
|
|
|
|
|
|
-string CompilerMSL::get_type_address_space(const SPIRType &type)
|
|
|
+string CompilerMSL::get_type_address_space(const SPIRType &type, uint32_t id)
|
|
|
{
|
|
|
switch (type.storage)
|
|
|
{
|
|
|
@@ -5401,8 +5450,16 @@ string CompilerMSL::get_type_address_space(const SPIRType &type)
|
|
|
return "threadgroup";
|
|
|
|
|
|
case StorageClassStorageBuffer:
|
|
|
- // FIXME: Need to use 'const device' for pointers into non-writable SSBOs
|
|
|
- return "device";
|
|
|
+ {
|
|
|
+ // This can be called for variable pointer contexts as well, so be very careful about which method we choose.
|
|
|
+ Bitset flags;
|
|
|
+ if (ir.ids[id].get_type() == TypeVariable && has_decoration(type.self, DecorationBlock))
|
|
|
+ flags = get_buffer_block_flags(id);
|
|
|
+ else
|
|
|
+ flags = get_decoration_bitset(id);
|
|
|
+
|
|
|
+ return flags.get(DecorationNonWritable) ? "const device" : "device";
|
|
|
+ }
|
|
|
|
|
|
case StorageClassUniform:
|
|
|
case StorageClassUniformConstant:
|
|
|
@@ -5410,9 +5467,17 @@ string CompilerMSL::get_type_address_space(const SPIRType &type)
|
|
|
if (type.basetype == SPIRType::Struct)
|
|
|
{
|
|
|
bool ssbo = has_decoration(type.self, DecorationBufferBlock);
|
|
|
- // FIXME: Need to use 'const device' for pointers into non-writable SSBOs
|
|
|
if (ssbo)
|
|
|
- return "device";
|
|
|
+ {
|
|
|
+ // This can be called for variable pointer contexts as well, so be very careful about which method we choose.
|
|
|
+ Bitset flags;
|
|
|
+ if (ir.ids[id].get_type() == TypeVariable && has_decoration(type.self, DecorationBlock))
|
|
|
+ flags = get_buffer_block_flags(id);
|
|
|
+ else
|
|
|
+ flags = get_decoration_bitset(id);
|
|
|
+
|
|
|
+ return flags.get(DecorationNonWritable) ? "const device" : "device";
|
|
|
+ }
|
|
|
else
|
|
|
return "constant";
|
|
|
}
|
|
|
@@ -5435,10 +5500,9 @@ string CompilerMSL::get_type_address_space(const SPIRType &type)
|
|
|
return "thread";
|
|
|
}
|
|
|
|
|
|
-// Returns a string containing a comma-delimited list of args for the entry point function
|
|
|
-string CompilerMSL::entry_point_args(bool append_comma)
|
|
|
+string CompilerMSL::entry_point_arg_stage_in()
|
|
|
{
|
|
|
- string ep_args;
|
|
|
+ string decl;
|
|
|
|
|
|
// Stage-in structure
|
|
|
uint32_t stage_in_id;
|
|
|
@@ -5452,19 +5516,137 @@ string CompilerMSL::entry_point_args(bool append_comma)
|
|
|
auto &var = get<SPIRVariable>(stage_in_id);
|
|
|
auto &type = get_variable_data_type(var);
|
|
|
|
|
|
+ add_resource_name(var.self);
|
|
|
+ decl = join(type_to_glsl(type), " ", to_name(var.self), " [[stage_in]]");
|
|
|
+ }
|
|
|
+
|
|
|
+ return decl;
|
|
|
+}
|
|
|
+
|
|
|
+void CompilerMSL::entry_point_args_builtin(string &ep_args)
|
|
|
+{
|
|
|
+ // Builtin variables
|
|
|
+ ir.for_each_typed_id<SPIRVariable>([&](uint32_t var_id, SPIRVariable &var) {
|
|
|
+ BuiltIn bi_type = ir.meta[var_id].decoration.builtin_type;
|
|
|
+
|
|
|
+ // Don't emit SamplePosition as a separate parameter. In the entry
|
|
|
+ // point, we get that by calling get_sample_position() on the sample ID.
|
|
|
+ if (var.storage == StorageClassInput && is_builtin_variable(var) &&
|
|
|
+ get_variable_data_type(var).basetype != SPIRType::Struct &&
|
|
|
+ get_variable_data_type(var).basetype != SPIRType::ControlPointArray)
|
|
|
+ {
|
|
|
+ if (bi_type != BuiltInSamplePosition && bi_type != BuiltInHelperInvocation &&
|
|
|
+ bi_type != BuiltInPatchVertices && bi_type != BuiltInTessLevelInner &&
|
|
|
+ bi_type != BuiltInTessLevelOuter && bi_type != BuiltInPosition && bi_type != BuiltInPointSize &&
|
|
|
+ bi_type != BuiltInClipDistance && bi_type != BuiltInCullDistance)
|
|
|
+ {
|
|
|
+ if (!ep_args.empty())
|
|
|
+ ep_args += ", ";
|
|
|
+
|
|
|
+ ep_args += builtin_type_decl(bi_type) + " " + to_expression(var_id);
|
|
|
+ ep_args += " [[" + builtin_qualifier(bi_type) + "]]";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // Vertex and instance index built-ins
|
|
|
+ if (needs_vertex_idx_arg)
|
|
|
+ ep_args += built_in_func_arg(BuiltInVertexIndex, !ep_args.empty());
|
|
|
+
|
|
|
+ if (needs_instance_idx_arg)
|
|
|
+ ep_args += built_in_func_arg(BuiltInInstanceIndex, !ep_args.empty());
|
|
|
+
|
|
|
+ if (capture_output_to_buffer)
|
|
|
+ {
|
|
|
+ // Add parameters to hold the indirect draw parameters and the shader output. This has to be handled
|
|
|
+ // specially because it needs to be a pointer, not a reference.
|
|
|
+ if (stage_out_var_id)
|
|
|
+ {
|
|
|
+ if (!ep_args.empty())
|
|
|
+ ep_args += ", ";
|
|
|
+ ep_args += join("device ", type_to_glsl(get_stage_out_struct_type()), "* ", output_buffer_var_name,
|
|
|
+ " [[buffer(", msl_options.shader_output_buffer_index, ")]]");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (stage_out_var_id || get_execution_model() == ExecutionModelTessellationControl)
|
|
|
+ {
|
|
|
+ if (!ep_args.empty())
|
|
|
+ ep_args += ", ";
|
|
|
+ ep_args +=
|
|
|
+ join("device uint* spvIndirectParams [[buffer(", msl_options.indirect_params_buffer_index, ")]]");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Tessellation control shaders get three additional parameters:
|
|
|
+ // a buffer to hold the per-patch data, a buffer to hold the per-patch
|
|
|
+ // tessellation levels, and a block of workgroup memory to hold the
|
|
|
+ // input control point data.
|
|
|
+ if (get_execution_model() == ExecutionModelTessellationControl)
|
|
|
+ {
|
|
|
+ if (patch_stage_out_var_id)
|
|
|
+ {
|
|
|
+ if (!ep_args.empty())
|
|
|
+ ep_args += ", ";
|
|
|
+ ep_args +=
|
|
|
+ join("device ", type_to_glsl(get_patch_stage_out_struct_type()), "* ", patch_output_buffer_var_name,
|
|
|
+ " [[buffer(", convert_to_string(msl_options.shader_patch_output_buffer_index), ")]]");
|
|
|
+ }
|
|
|
+ if (!ep_args.empty())
|
|
|
+ ep_args += ", ";
|
|
|
+ ep_args += join("device ", get_tess_factor_struct_name(), "* ", tess_factor_buffer_var_name, " [[buffer(",
|
|
|
+ convert_to_string(msl_options.shader_tess_factor_buffer_index), ")]]");
|
|
|
+ if (stage_in_var_id)
|
|
|
+ {
|
|
|
+ if (!ep_args.empty())
|
|
|
+ ep_args += ", ";
|
|
|
+ ep_args += join("threadgroup ", type_to_glsl(get_stage_in_struct_type()), "* ", input_wg_var_name,
|
|
|
+ " [[threadgroup(", convert_to_string(msl_options.shader_input_wg_index), ")]]");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+string CompilerMSL::entry_point_args_argument_buffer(bool append_comma)
|
|
|
+{
|
|
|
+ string ep_args = entry_point_arg_stage_in();
|
|
|
+
|
|
|
+ for (uint32_t i = 0; i < kMaxArgumentBuffers; i++)
|
|
|
+ {
|
|
|
+ uint32_t id = argument_buffer_ids[i];
|
|
|
+ if (id == 0)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ add_resource_name(id);
|
|
|
+ auto &var = get<SPIRVariable>(id);
|
|
|
+ auto &type = get_variable_data_type(var);
|
|
|
+
|
|
|
if (!ep_args.empty())
|
|
|
ep_args += ", ";
|
|
|
|
|
|
- add_resource_name(var.self);
|
|
|
- ep_args += join(type_to_glsl(type), " ", to_name(var.self), " [[stage_in]]");
|
|
|
+ ep_args += get_argument_address_space(var) + " " + type_to_glsl(type) + "& " + to_name(id);
|
|
|
+ ep_args += " [[buffer(" + convert_to_string(i) + ")]]";
|
|
|
+
|
|
|
+ // Makes it more practical for testing, since the push constant block can occupy the first available
|
|
|
+ // buffer slot if it's not bound explicitly.
|
|
|
+ next_metal_resource_index_buffer = i + 1;
|
|
|
}
|
|
|
|
|
|
+ entry_point_args_discrete_descriptors(ep_args);
|
|
|
+ entry_point_args_builtin(ep_args);
|
|
|
+
|
|
|
+ if (!ep_args.empty() && append_comma)
|
|
|
+ ep_args += ", ";
|
|
|
+
|
|
|
+ return ep_args;
|
|
|
+}
|
|
|
+
|
|
|
+void CompilerMSL::entry_point_args_discrete_descriptors(string &ep_args)
|
|
|
+{
|
|
|
// Output resources, sorted by resource index & type
|
|
|
// We need to sort to work around a bug on macOS 10.13 with NVidia drivers where switching between shaders
|
|
|
// with different order of buffers can result in issues with buffer assignments inside the driver.
|
|
|
struct Resource
|
|
|
{
|
|
|
- Variant *id;
|
|
|
+ SPIRVariable *var;
|
|
|
string name;
|
|
|
SPIRType::BaseType basetype;
|
|
|
uint32_t index;
|
|
|
@@ -5472,25 +5654,30 @@ string CompilerMSL::entry_point_args(bool append_comma)
|
|
|
|
|
|
vector<Resource> resources;
|
|
|
|
|
|
- ir.for_each_typed_id<SPIRVariable>([&](uint32_t self, SPIRVariable &var) {
|
|
|
- auto &id = ir.ids[self];
|
|
|
- auto &type = get_variable_data_type(var);
|
|
|
-
|
|
|
- uint32_t var_id = var.self;
|
|
|
-
|
|
|
+ ir.for_each_typed_id<SPIRVariable>([&](uint32_t, SPIRVariable &var) {
|
|
|
if ((var.storage == StorageClassUniform || var.storage == StorageClassUniformConstant ||
|
|
|
var.storage == StorageClassPushConstant || var.storage == StorageClassStorageBuffer) &&
|
|
|
!is_hidden_variable(var))
|
|
|
{
|
|
|
+ auto &type = get_variable_data_type(var);
|
|
|
+ uint32_t var_id = var.self;
|
|
|
+
|
|
|
+ if (var.storage != StorageClassPushConstant)
|
|
|
+ {
|
|
|
+ uint32_t desc_set = get_decoration(var_id, DecorationDescriptorSet);
|
|
|
+ if (descriptor_set_is_argument_buffer(desc_set))
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
if (type.basetype == SPIRType::SampledImage)
|
|
|
{
|
|
|
add_resource_name(var_id);
|
|
|
resources.push_back(
|
|
|
- { &id, to_name(var_id), SPIRType::Image, get_metal_resource_index(var, SPIRType::Image) });
|
|
|
+ { &var, to_name(var_id), SPIRType::Image, get_metal_resource_index(var, SPIRType::Image) });
|
|
|
|
|
|
if (type.image.dim != DimBuffer && constexpr_samplers.count(var_id) == 0)
|
|
|
{
|
|
|
- resources.push_back({ &id, to_sampler_expression(var_id), SPIRType::Sampler,
|
|
|
+ resources.push_back({ &var, to_sampler_expression(var_id), SPIRType::Sampler,
|
|
|
get_metal_resource_index(var, SPIRType::Sampler) });
|
|
|
}
|
|
|
}
|
|
|
@@ -5499,18 +5686,18 @@ string CompilerMSL::entry_point_args(bool append_comma)
|
|
|
// constexpr samplers are not declared as resources.
|
|
|
add_resource_name(var_id);
|
|
|
resources.push_back(
|
|
|
- { &id, to_name(var_id), type.basetype, get_metal_resource_index(var, type.basetype) });
|
|
|
+ { &var, to_name(var_id), type.basetype, get_metal_resource_index(var, type.basetype) });
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
- std::sort(resources.begin(), resources.end(), [](const Resource &lhs, const Resource &rhs) {
|
|
|
+ sort(resources.begin(), resources.end(), [](const Resource &lhs, const Resource &rhs) {
|
|
|
return tie(lhs.basetype, lhs.index) < tie(rhs.basetype, rhs.index);
|
|
|
});
|
|
|
|
|
|
for (auto &r : resources)
|
|
|
{
|
|
|
- auto &var = r.id->get<SPIRVariable>();
|
|
|
+ auto &var = *r.var;
|
|
|
auto &type = get_variable_data_type(var);
|
|
|
|
|
|
uint32_t var_id = var.self;
|
|
|
@@ -5571,85 +5758,15 @@ string CompilerMSL::entry_point_args(bool append_comma)
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- // Builtin variables
|
|
|
- ir.for_each_typed_id<SPIRVariable>([&](uint32_t var_id, SPIRVariable &var) {
|
|
|
- BuiltIn bi_type = ir.meta[var_id].decoration.builtin_type;
|
|
|
-
|
|
|
- // Don't emit SamplePosition as a separate parameter. In the entry
|
|
|
- // point, we get that by calling get_sample_position() on the sample ID.
|
|
|
- if (var.storage == StorageClassInput && is_builtin_variable(var) &&
|
|
|
- get_variable_data_type(var).basetype != SPIRType::Struct &&
|
|
|
- get_variable_data_type(var).basetype != SPIRType::ControlPointArray)
|
|
|
- {
|
|
|
- if (bi_type != BuiltInSamplePosition && bi_type != BuiltInHelperInvocation &&
|
|
|
- bi_type != BuiltInPatchVertices && bi_type != BuiltInTessLevelInner &&
|
|
|
- bi_type != BuiltInTessLevelOuter && bi_type != BuiltInPosition && bi_type != BuiltInPointSize &&
|
|
|
- bi_type != BuiltInClipDistance && bi_type != BuiltInCullDistance)
|
|
|
- {
|
|
|
- if (!ep_args.empty())
|
|
|
- ep_args += ", ";
|
|
|
-
|
|
|
- ep_args += builtin_type_decl(bi_type) + " " + to_expression(var_id);
|
|
|
- ep_args += " [[" + builtin_qualifier(bi_type) + "]]";
|
|
|
- }
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- // Vertex and instance index built-ins
|
|
|
- if (needs_vertex_idx_arg)
|
|
|
- ep_args += built_in_func_arg(BuiltInVertexIndex, !ep_args.empty());
|
|
|
-
|
|
|
- if (needs_instance_idx_arg)
|
|
|
- ep_args += built_in_func_arg(BuiltInInstanceIndex, !ep_args.empty());
|
|
|
-
|
|
|
- if (capture_output_to_buffer)
|
|
|
- {
|
|
|
- // Add parameters to hold the indirect draw parameters and the shader output. This has to be handled
|
|
|
- // specially because it needs to be a pointer, not a reference.
|
|
|
- if (stage_out_var_id)
|
|
|
- {
|
|
|
- if (!ep_args.empty())
|
|
|
- ep_args += ", ";
|
|
|
- ep_args += join("device ", type_to_glsl(get_stage_out_struct_type()), "* ", output_buffer_var_name,
|
|
|
- " [[buffer(", msl_options.shader_output_buffer_index, ")]]");
|
|
|
- }
|
|
|
-
|
|
|
- if (stage_out_var_id || get_execution_model() == ExecutionModelTessellationControl)
|
|
|
- {
|
|
|
- if (!ep_args.empty())
|
|
|
- ep_args += ", ";
|
|
|
- ep_args +=
|
|
|
- join("device uint* spvIndirectParams [[buffer(", msl_options.indirect_params_buffer_index, ")]]");
|
|
|
- }
|
|
|
-
|
|
|
- // Tessellation control shaders get three additional parameters:
|
|
|
- // a buffer to hold the per-patch data, a buffer to hold the per-patch
|
|
|
- // tessellation levels, and a block of workgroup memory to hold the
|
|
|
- // input control point data.
|
|
|
- if (get_execution_model() == ExecutionModelTessellationControl)
|
|
|
- {
|
|
|
- if (patch_stage_out_var_id)
|
|
|
- {
|
|
|
- if (!ep_args.empty())
|
|
|
- ep_args += ", ";
|
|
|
- ep_args +=
|
|
|
- join("device ", type_to_glsl(get_patch_stage_out_struct_type()), "* ", patch_output_buffer_var_name,
|
|
|
- " [[buffer(", convert_to_string(msl_options.shader_patch_output_buffer_index), ")]]");
|
|
|
- }
|
|
|
- if (!ep_args.empty())
|
|
|
- ep_args += ", ";
|
|
|
- ep_args += join("device ", get_tess_factor_struct_name(), "* ", tess_factor_buffer_var_name, " [[buffer(",
|
|
|
- convert_to_string(msl_options.shader_tess_factor_buffer_index), ")]]");
|
|
|
- if (stage_in_var_id)
|
|
|
- {
|
|
|
- if (!ep_args.empty())
|
|
|
- ep_args += ", ";
|
|
|
- ep_args += join("threadgroup ", type_to_glsl(get_stage_in_struct_type()), "* ", input_wg_var_name,
|
|
|
- " [[threadgroup(", convert_to_string(msl_options.shader_input_wg_index), ")]]");
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+// Returns a string containing a comma-delimited list of args for the entry point function
|
|
|
+// This is the "classic" method of MSL 1 when we don't have argument buffer support.
|
|
|
+string CompilerMSL::entry_point_args_classic(bool append_comma)
|
|
|
+{
|
|
|
+ string ep_args = entry_point_arg_stage_in();
|
|
|
+ entry_point_args_discrete_descriptors(ep_args);
|
|
|
+ entry_point_args_builtin(ep_args);
|
|
|
|
|
|
if (!ep_args.empty() && append_comma)
|
|
|
ep_args += ", ";
|
|
|
@@ -5871,6 +5988,22 @@ string CompilerMSL::argument_decl(const SPIRFunction::Parameter &arg)
|
|
|
// Arrays of images and samplers are special cased.
|
|
|
if (!address_space.empty())
|
|
|
decl = join(address_space, " ", decl);
|
|
|
+
|
|
|
+ if (msl_options.argument_buffers)
|
|
|
+ {
|
|
|
+ // An awkward case where we need to emit *more* address space declarations (yay!).
|
|
|
+ // An example is where we pass down an array of buffer pointers to leaf functions.
|
|
|
+ // It's a constant array containing pointers to constants.
|
|
|
+ // The pointer array is always constant however. E.g.
|
|
|
+ // device SSBO * constant (&array)[N].
|
|
|
+ // const device SSBO * constant (&array)[N].
|
|
|
+ // constant SSBO * constant (&array)[N].
|
|
|
+ // However, this only matters for argument buffers, since for MSL 1.0 style codegen,
|
|
|
+ // we emit the buffer array on stack instead, and that seems to work just fine apparently.
|
|
|
+ if (storage == StorageClassUniform || storage == StorageClassStorageBuffer)
|
|
|
+ decl += " constant";
|
|
|
+ }
|
|
|
+
|
|
|
decl += " (&";
|
|
|
decl += to_expression(name_id);
|
|
|
decl += ")";
|
|
|
@@ -6230,9 +6363,16 @@ string CompilerMSL::to_member_reference(uint32_t base, const SPIRType &type, uin
|
|
|
auto *var = maybe_get<SPIRVariable>(base);
|
|
|
// If this is a buffer array, we have to dereference the buffer pointers.
|
|
|
// Otherwise, if this is a pointer expression, dereference it.
|
|
|
- if ((var && ((var->storage == StorageClassUniform || var->storage == StorageClassStorageBuffer) &&
|
|
|
- is_array(get<SPIRType>(var->basetype)))) ||
|
|
|
- (!ptr_chain && should_dereference(base)))
|
|
|
+
|
|
|
+ bool declared_as_pointer = false;
|
|
|
+
|
|
|
+ if (var)
|
|
|
+ {
|
|
|
+ bool is_buffer_variable = var->storage == StorageClassUniform || var->storage == StorageClassStorageBuffer;
|
|
|
+ declared_as_pointer = is_buffer_variable && is_array(get<SPIRType>(var->basetype));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (declared_as_pointer || (!ptr_chain && should_dereference(base)))
|
|
|
return join("->", to_member_name(type, index));
|
|
|
else
|
|
|
return join(".", to_member_name(type, index));
|
|
|
@@ -6259,7 +6399,7 @@ string CompilerMSL::type_to_glsl(const SPIRType &type, uint32_t id)
|
|
|
// Pointer?
|
|
|
if (type.pointer)
|
|
|
{
|
|
|
- type_name = join(get_type_address_space(type), " ", type_to_glsl(get<SPIRType>(type.parent_type), id));
|
|
|
+ type_name = join(get_type_address_space(type, id), " ", type_to_glsl(get<SPIRType>(type.parent_type), id));
|
|
|
switch (type.basetype)
|
|
|
{
|
|
|
case SPIRType::Image:
|
|
|
@@ -7391,3 +7531,174 @@ std::string CompilerMSL::to_initializer_expression(const SPIRVariable &var)
|
|
|
else
|
|
|
return CompilerGLSL::to_initializer_expression(var);
|
|
|
}
|
|
|
+
|
|
|
+bool CompilerMSL::descriptor_set_is_argument_buffer(uint32_t desc_set) const
|
|
|
+{
|
|
|
+ if (!msl_options.argument_buffers)
|
|
|
+ return false;
|
|
|
+ if (desc_set >= kMaxArgumentBuffers)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ return (argument_buffer_discrete_mask & (1u << desc_set)) == 0;
|
|
|
+}
|
|
|
+
|
|
|
+void CompilerMSL::analyze_argument_buffers()
|
|
|
+{
|
|
|
+ // Gather all used resources and sort them out into argument buffers.
|
|
|
+ // Each argument buffer corresponds to a descriptor set in SPIR-V.
|
|
|
+ // The [[id(N)]] values used correspond to the resource mapping we have for MSL.
|
|
|
+ // Otherwise, the binding number is used, but this is generally not safe some types like
|
|
|
+ // combined image samplers and arrays of resources. Metal needs different indices here,
|
|
|
+ // while SPIR-V can have one descriptor set binding. To use argument buffers in practice,
|
|
|
+ // you will need to use the remapping from the API.
|
|
|
+ for (auto &id : argument_buffer_ids)
|
|
|
+ id = 0;
|
|
|
+
|
|
|
+ // Output resources, sorted by resource index & type.
|
|
|
+ struct Resource
|
|
|
+ {
|
|
|
+ SPIRVariable *var;
|
|
|
+ string name;
|
|
|
+ SPIRType::BaseType basetype;
|
|
|
+ uint32_t index;
|
|
|
+ };
|
|
|
+ vector<Resource> resources_in_set[kMaxArgumentBuffers];
|
|
|
+
|
|
|
+ ir.for_each_typed_id<SPIRVariable>([&](uint32_t self, SPIRVariable &var) {
|
|
|
+ if ((var.storage == StorageClassUniform || var.storage == StorageClassUniformConstant ||
|
|
|
+ var.storage == StorageClassStorageBuffer) &&
|
|
|
+ !is_hidden_variable(var))
|
|
|
+ {
|
|
|
+ uint32_t desc_set = get_decoration(self, DecorationDescriptorSet);
|
|
|
+ // Ignore if it's part of a push descriptor set.
|
|
|
+ if (!descriptor_set_is_argument_buffer(desc_set))
|
|
|
+ return;
|
|
|
+
|
|
|
+ uint32_t var_id = var.self;
|
|
|
+ auto &type = get_variable_data_type(var);
|
|
|
+
|
|
|
+ if (desc_set >= kMaxArgumentBuffers)
|
|
|
+ SPIRV_CROSS_THROW("Descriptor set index is out of range.");
|
|
|
+
|
|
|
+ if (type.basetype == SPIRType::SampledImage)
|
|
|
+ {
|
|
|
+ add_resource_name(var_id);
|
|
|
+
|
|
|
+ uint32_t image_resource_index = get_metal_resource_index(var, SPIRType::Image);
|
|
|
+ uint32_t sampler_resource_index = get_metal_resource_index(var, SPIRType::Sampler);
|
|
|
+
|
|
|
+ // Avoid trivial conflicts where we didn't remap.
|
|
|
+ // This will let us at least compile test cases without having to instrument remaps.
|
|
|
+ if (sampler_resource_index == image_resource_index)
|
|
|
+ sampler_resource_index += type.array.empty() ? 1 : to_array_size_literal(type);
|
|
|
+
|
|
|
+ resources_in_set[desc_set].push_back({ &var, to_name(var_id), SPIRType::Image, image_resource_index });
|
|
|
+
|
|
|
+ if (type.image.dim != DimBuffer && constexpr_samplers.count(var_id) == 0)
|
|
|
+ {
|
|
|
+ resources_in_set[desc_set].push_back(
|
|
|
+ { &var, to_sampler_expression(var_id), SPIRType::Sampler, sampler_resource_index });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (constexpr_samplers.count(var_id) == 0)
|
|
|
+ {
|
|
|
+ // constexpr samplers are not declared as resources.
|
|
|
+ add_resource_name(var_id);
|
|
|
+ resources_in_set[desc_set].push_back(
|
|
|
+ { &var, to_name(var_id), type.basetype, get_metal_resource_index(var, type.basetype) });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ for (uint32_t desc_set = 0; desc_set < kMaxArgumentBuffers; desc_set++)
|
|
|
+ {
|
|
|
+ auto &resources = resources_in_set[desc_set];
|
|
|
+ if (resources.empty())
|
|
|
+ continue;
|
|
|
+
|
|
|
+ assert(descriptor_set_is_argument_buffer(desc_set));
|
|
|
+
|
|
|
+ uint32_t next_id = ir.increase_bound_by(3);
|
|
|
+ uint32_t type_id = next_id + 1;
|
|
|
+ uint32_t ptr_type_id = next_id + 2;
|
|
|
+ argument_buffer_ids[desc_set] = next_id;
|
|
|
+
|
|
|
+ auto &buffer_type = set<SPIRType>(type_id);
|
|
|
+ buffer_type.storage = StorageClassUniform;
|
|
|
+ buffer_type.basetype = SPIRType::Struct;
|
|
|
+ set_name(type_id, join("spvDescriptorSetBuffer", desc_set));
|
|
|
+
|
|
|
+ auto &ptr_type = set<SPIRType>(ptr_type_id);
|
|
|
+ ptr_type = buffer_type;
|
|
|
+ ptr_type.pointer = true;
|
|
|
+ ptr_type.pointer_depth = 1;
|
|
|
+ ptr_type.parent_type = type_id;
|
|
|
+
|
|
|
+ uint32_t buffer_variable_id = next_id;
|
|
|
+ set<SPIRVariable>(buffer_variable_id, ptr_type_id, StorageClassUniform);
|
|
|
+ set_name(buffer_variable_id, join("spvDescriptorSet", desc_set));
|
|
|
+
|
|
|
+ // Ids must be emitted in ID order.
|
|
|
+ sort(begin(resources), end(resources), [&](const Resource &lhs, const Resource &rhs) -> bool {
|
|
|
+ return tie(lhs.index, lhs.basetype) < tie(rhs.index, rhs.basetype);
|
|
|
+ });
|
|
|
+
|
|
|
+ uint32_t member_index = 0;
|
|
|
+ for (auto &resource : resources)
|
|
|
+ {
|
|
|
+ auto &var = *resource.var;
|
|
|
+ auto &type = get_variable_data_type(var);
|
|
|
+ string mbr_name = ensure_valid_name(resource.name, "m");
|
|
|
+ set_member_name(buffer_type.self, member_index, mbr_name);
|
|
|
+
|
|
|
+ if (resource.basetype == SPIRType::Sampler && type.basetype != SPIRType::Sampler)
|
|
|
+ {
|
|
|
+ // Have to synthesize a sampler type here.
|
|
|
+
|
|
|
+ bool type_is_array = !type.array.empty();
|
|
|
+ uint32_t sampler_type_id = ir.increase_bound_by(type_is_array ? 2 : 1);
|
|
|
+ auto &new_sampler_type = set<SPIRType>(sampler_type_id);
|
|
|
+ new_sampler_type.basetype = SPIRType::Sampler;
|
|
|
+ new_sampler_type.storage = StorageClassUniformConstant;
|
|
|
+
|
|
|
+ if (type_is_array)
|
|
|
+ {
|
|
|
+ uint32_t sampler_type_array_id = sampler_type_id + 1;
|
|
|
+ auto &sampler_type_array = set<SPIRType>(sampler_type_array_id);
|
|
|
+ sampler_type_array = new_sampler_type;
|
|
|
+ sampler_type_array.array = type.array;
|
|
|
+ sampler_type_array.array_size_literal = type.array_size_literal;
|
|
|
+ sampler_type_array.parent_type = sampler_type_id;
|
|
|
+ buffer_type.member_types.push_back(sampler_type_array_id);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ buffer_type.member_types.push_back(sampler_type_id);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (resource.basetype == SPIRType::Image || resource.basetype == SPIRType::Sampler ||
|
|
|
+ resource.basetype == SPIRType::SampledImage)
|
|
|
+ {
|
|
|
+ // Drop pointer information when we emit the resources into a struct.
|
|
|
+ buffer_type.member_types.push_back(get_variable_data_type_id(var));
|
|
|
+ set_qualified_name(var.self, join(to_name(buffer_variable_id), ".", mbr_name));
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Resources will be declared as pointers not references, so automatically dereference as appropriate.
|
|
|
+ buffer_type.member_types.push_back(var.basetype);
|
|
|
+ if (type.array.empty())
|
|
|
+ set_qualified_name(var.self, join("(*", to_name(buffer_variable_id), ".", mbr_name, ")"));
|
|
|
+ else
|
|
|
+ set_qualified_name(var.self, join(to_name(buffer_variable_id), ".", mbr_name));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ set_extended_member_decoration(buffer_type.self, member_index, SPIRVCrossDecorationArgumentBufferID,
|
|
|
+ resource.index);
|
|
|
+ set_extended_member_decoration(buffer_type.self, member_index, SPIRVCrossDecorationInterfaceOrigID,
|
|
|
+ var.self);
|
|
|
+ member_index++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|