blob: a4f911c3cbfa5d5f915f113e33ffcaf21e578930 [file] [log] [blame]
/*
* Copyright 2016-2019 The Brenwill Workshop Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "spirv_msl.hpp"
#include "GLSL.std.450.h"
#include <algorithm>
#include <assert.h>
#include <numeric>
using namespace spv;
using namespace SPIRV_CROSS_NAMESPACE;
using namespace std;
static const uint32_t k_unknown_location = ~0u;
static const uint32_t k_unknown_component = ~0u;
CompilerMSL::CompilerMSL(std::vector<uint32_t> spirv_)
: CompilerGLSL(move(spirv_))
{
}
CompilerMSL::CompilerMSL(const uint32_t *ir_, size_t word_count)
: CompilerGLSL(ir_, word_count)
{
}
CompilerMSL::CompilerMSL(const ParsedIR &ir_)
: CompilerGLSL(ir_)
{
}
CompilerMSL::CompilerMSL(ParsedIR &&ir_)
: CompilerGLSL(std::move(ir_))
{
}
void CompilerMSL::add_msl_vertex_attribute(const MSLVertexAttr &va)
{
vtx_attrs_by_location[va.location] = va;
if (va.builtin != BuiltInMax && !vtx_attrs_by_builtin.count(va.builtin))
vtx_attrs_by_builtin[va.builtin] = va;
}
void CompilerMSL::add_msl_resource_binding(const MSLResourceBinding &binding)
{
StageSetBinding tuple = { binding.stage, binding.desc_set, binding.binding };
resource_bindings[tuple] = { 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;
}
bool CompilerMSL::is_msl_resource_binding_used(ExecutionModel model, uint32_t desc_set, uint32_t binding)
{
StageSetBinding tuple = { model, desc_set, binding };
auto itr = resource_bindings.find(tuple);
return itr != end(resource_bindings) && itr->second.second;
}
uint32_t CompilerMSL::get_automatic_msl_resource_binding(uint32_t id) const
{
return get_extended_decoration(id, SPIRVCrossDecorationResourceIndexPrimary);
}
uint32_t CompilerMSL::get_automatic_msl_resource_binding_secondary(uint32_t id) const
{
return get_extended_decoration(id, SPIRVCrossDecorationResourceIndexSecondary);
}
void CompilerMSL::set_fragment_output_components(uint32_t location, uint32_t components)
{
fragment_output_components[location] = components;
}
void CompilerMSL::build_implicit_builtins()
{
bool need_sample_pos = active_input_builtins.get(BuiltInSamplePosition);
bool need_vertex_params = capture_output_to_buffer && get_execution_model() == ExecutionModelVertex;
bool need_tesc_params = get_execution_model() == ExecutionModelTessellationControl;
bool need_subgroup_mask =
active_input_builtins.get(BuiltInSubgroupEqMask) || active_input_builtins.get(BuiltInSubgroupGeMask) ||
active_input_builtins.get(BuiltInSubgroupGtMask) || active_input_builtins.get(BuiltInSubgroupLeMask) ||
active_input_builtins.get(BuiltInSubgroupLtMask);
bool need_subgroup_ge_mask = !msl_options.is_ios() && (active_input_builtins.get(BuiltInSubgroupGeMask) ||
active_input_builtins.get(BuiltInSubgroupGtMask));
bool need_multiview = get_execution_model() == ExecutionModelVertex && !msl_options.view_index_from_device_index &&
(msl_options.multiview || active_input_builtins.get(BuiltInViewIndex));
bool need_dispatch_base =
msl_options.dispatch_base && get_execution_model() == ExecutionModelGLCompute &&
(active_input_builtins.get(BuiltInWorkgroupId) || active_input_builtins.get(BuiltInGlobalInvocationId));
if (need_subpass_input || need_sample_pos || need_subgroup_mask || need_vertex_params || need_tesc_params ||
need_multiview || need_dispatch_base || needs_subgroup_invocation_id)
{
bool has_frag_coord = false;
bool has_sample_id = false;
bool has_vertex_idx = false;
bool has_base_vertex = false;
bool has_instance_idx = false;
bool has_base_instance = false;
bool has_invocation_id = false;
bool has_primitive_id = false;
bool has_subgroup_invocation_id = false;
bool has_subgroup_size = false;
bool has_view_idx = false;
uint32_t workgroup_id_type = 0;
ir.for_each_typed_id<SPIRVariable>([&](uint32_t, SPIRVariable &var) {
if (var.storage != StorageClassInput || !ir.meta[var.self].decoration.builtin)
return;
BuiltIn builtin = ir.meta[var.self].decoration.builtin_type;
if (need_subpass_input && builtin == BuiltInFragCoord)
{
builtin_frag_coord_id = var.self;
has_frag_coord = true;
}
if (need_sample_pos && builtin == BuiltInSampleId)
{
builtin_sample_id_id = var.self;
has_sample_id = true;
}
if (need_vertex_params)
{
switch (builtin)
{
case BuiltInVertexIndex:
builtin_vertex_idx_id = var.self;
has_vertex_idx = true;
break;
case BuiltInBaseVertex:
builtin_base_vertex_id = var.self;
has_base_vertex = true;
break;
case BuiltInInstanceIndex:
builtin_instance_idx_id = var.self;
has_instance_idx = true;
break;
case BuiltInBaseInstance:
builtin_base_instance_id = var.self;
has_base_instance = true;
break;
default:
break;
}
}
if (need_tesc_params)
{
switch (builtin)
{
case BuiltInInvocationId:
builtin_invocation_id_id = var.self;
has_invocation_id = true;
break;
case BuiltInPrimitiveId:
builtin_primitive_id_id = var.self;
has_primitive_id = true;
break;
default:
break;
}
}
if ((need_subgroup_mask || needs_subgroup_invocation_id) && builtin == BuiltInSubgroupLocalInvocationId)
{
builtin_subgroup_invocation_id_id = var.self;
has_subgroup_invocation_id = true;
}
if (need_subgroup_ge_mask && builtin == BuiltInSubgroupSize)
{
builtin_subgroup_size_id = var.self;
has_subgroup_size = true;
}
if (need_multiview)
{
if (builtin == BuiltInInstanceIndex)
{
// The view index here is derived from the instance index.
builtin_instance_idx_id = var.self;
has_instance_idx = true;
}
if (builtin == BuiltInViewIndex)
{
builtin_view_idx_id = var.self;
has_view_idx = true;
}
}
// The base workgroup needs to have the same type and vector size
// as the workgroup or invocation ID, so keep track of the type that
// was used.
if (need_dispatch_base && workgroup_id_type == 0 &&
(builtin == BuiltInWorkgroupId || builtin == BuiltInGlobalInvocationId))
workgroup_id_type = var.basetype;
});
if (!has_frag_coord && need_subpass_input)
{
uint32_t offset = ir.increase_bound_by(3);
uint32_t type_id = offset;
uint32_t type_ptr_id = offset + 1;
uint32_t var_id = offset + 2;
// Create gl_FragCoord.
SPIRType vec4_type;
vec4_type.basetype = SPIRType::Float;
vec4_type.width = 32;
vec4_type.vecsize = 4;
set<SPIRType>(type_id, vec4_type);
SPIRType vec4_type_ptr;
vec4_type_ptr = vec4_type;
vec4_type_ptr.pointer = true;
vec4_type_ptr.parent_type = type_id;
vec4_type_ptr.storage = StorageClassInput;
auto &ptr_type = set<SPIRType>(type_ptr_id, vec4_type_ptr);
ptr_type.self = type_id;
set<SPIRVariable>(var_id, type_ptr_id, StorageClassInput);
set_decoration(var_id, DecorationBuiltIn, BuiltInFragCoord);
builtin_frag_coord_id = var_id;
mark_implicit_builtin(StorageClassInput, BuiltInFragCoord, var_id);
}
if (!has_sample_id && need_sample_pos)
{
uint32_t offset = ir.increase_bound_by(3);
uint32_t type_id = offset;
uint32_t type_ptr_id = offset + 1;
uint32_t var_id = offset + 2;
// Create gl_SampleID.
SPIRType uint_type;
uint_type.basetype = SPIRType::UInt;
uint_type.width = 32;
set<SPIRType>(type_id, uint_type);
SPIRType uint_type_ptr;
uint_type_ptr = uint_type;
uint_type_ptr.pointer = true;
uint_type_ptr.parent_type = type_id;
uint_type_ptr.storage = StorageClassInput;
auto &ptr_type = set<SPIRType>(type_ptr_id, uint_type_ptr);
ptr_type.self = type_id;
set<SPIRVariable>(var_id, type_ptr_id, StorageClassInput);
set_decoration(var_id, DecorationBuiltIn, BuiltInSampleId);
builtin_sample_id_id = var_id;
mark_implicit_builtin(StorageClassInput, BuiltInSampleId, var_id);
}
if ((need_vertex_params && (!has_vertex_idx || !has_base_vertex || !has_instance_idx || !has_base_instance)) ||
(need_multiview && (!has_instance_idx || !has_view_idx)))
{
uint32_t offset = ir.increase_bound_by(2);
uint32_t type_id = offset;
uint32_t type_ptr_id = offset + 1;
SPIRType uint_type;
uint_type.basetype = SPIRType::UInt;
uint_type.width = 32;
set<SPIRType>(type_id, uint_type);
SPIRType uint_type_ptr;
uint_type_ptr = uint_type;
uint_type_ptr.pointer = true;
uint_type_ptr.parent_type = type_id;
uint_type_ptr.storage = StorageClassInput;
auto &ptr_type = set<SPIRType>(type_ptr_id, uint_type_ptr);
ptr_type.self = type_id;
if (need_vertex_params && !has_vertex_idx)
{
uint32_t var_id = ir.increase_bound_by(1);
// Create gl_VertexIndex.
set<SPIRVariable>(var_id, type_ptr_id, StorageClassInput);
set_decoration(var_id, DecorationBuiltIn, BuiltInVertexIndex);
builtin_vertex_idx_id = var_id;
mark_implicit_builtin(StorageClassInput, BuiltInVertexIndex, var_id);
}
if (need_vertex_params && !has_base_vertex)
{
uint32_t var_id = ir.increase_bound_by(1);
// Create gl_BaseVertex.
set<SPIRVariable>(var_id, type_ptr_id, StorageClassInput);
set_decoration(var_id, DecorationBuiltIn, BuiltInBaseVertex);
builtin_base_vertex_id = var_id;
mark_implicit_builtin(StorageClassInput, BuiltInBaseVertex, var_id);
}
if (!has_instance_idx) // Needed by both multiview and tessellation
{
uint32_t var_id = ir.increase_bound_by(1);
// Create gl_InstanceIndex.
set<SPIRVariable>(var_id, type_ptr_id, StorageClassInput);
set_decoration(var_id, DecorationBuiltIn, BuiltInInstanceIndex);
builtin_instance_idx_id = var_id;
mark_implicit_builtin(StorageClassInput, BuiltInInstanceIndex, var_id);
}
if (need_vertex_params && !has_base_instance)
{
uint32_t var_id = ir.increase_bound_by(1);
// Create gl_BaseInstance.
set<SPIRVariable>(var_id, type_ptr_id, StorageClassInput);
set_decoration(var_id, DecorationBuiltIn, BuiltInBaseInstance);
builtin_base_instance_id = var_id;
mark_implicit_builtin(StorageClassInput, BuiltInBaseInstance, var_id);
}
if (need_multiview)
{
// Multiview shaders are not allowed to write to gl_Layer, ostensibly because
// it is implicitly written from gl_ViewIndex, but we have to do that explicitly.
// Note that we can't just abuse gl_ViewIndex for this purpose: it's an input, but
// gl_Layer is an output in vertex-pipeline shaders.
uint32_t type_ptr_out_id = ir.increase_bound_by(2);
SPIRType uint_type_ptr_out;
uint_type_ptr_out = uint_type;
uint_type_ptr_out.pointer = true;
uint_type_ptr_out.parent_type = type_id;
uint_type_ptr_out.storage = StorageClassOutput;
auto &ptr_out_type = set<SPIRType>(type_ptr_out_id, uint_type_ptr_out);
ptr_out_type.self = type_id;
uint32_t var_id = type_ptr_out_id + 1;
set<SPIRVariable>(var_id, type_ptr_out_id, StorageClassOutput);
set_decoration(var_id, DecorationBuiltIn, BuiltInLayer);
builtin_layer_id = var_id;
mark_implicit_builtin(StorageClassOutput, BuiltInLayer, var_id);
}
if (need_multiview && !has_view_idx)
{
uint32_t var_id = ir.increase_bound_by(1);
// Create gl_ViewIndex.
set<SPIRVariable>(var_id, type_ptr_id, StorageClassInput);
set_decoration(var_id, DecorationBuiltIn, BuiltInViewIndex);
builtin_view_idx_id = var_id;
mark_implicit_builtin(StorageClassInput, BuiltInViewIndex, var_id);
}
}
if (need_tesc_params && (!has_invocation_id || !has_primitive_id))
{
uint32_t offset = ir.increase_bound_by(2);
uint32_t type_id = offset;
uint32_t type_ptr_id = offset + 1;
SPIRType uint_type;
uint_type.basetype = SPIRType::UInt;
uint_type.width = 32;
set<SPIRType>(type_id, uint_type);
SPIRType uint_type_ptr;
uint_type_ptr = uint_type;
uint_type_ptr.pointer = true;
uint_type_ptr.parent_type = type_id;
uint_type_ptr.storage = StorageClassInput;
auto &ptr_type = set<SPIRType>(type_ptr_id, uint_type_ptr);
ptr_type.self = type_id;
if (!has_invocation_id)
{
uint32_t var_id = ir.increase_bound_by(1);
// Create gl_InvocationID.
set<SPIRVariable>(var_id, type_ptr_id, StorageClassInput);
set_decoration(var_id, DecorationBuiltIn, BuiltInInvocationId);
builtin_invocation_id_id = var_id;
mark_implicit_builtin(StorageClassInput, BuiltInInvocationId, var_id);
}
if (!has_primitive_id)
{
uint32_t var_id = ir.increase_bound_by(1);
// Create gl_PrimitiveID.
set<SPIRVariable>(var_id, type_ptr_id, StorageClassInput);
set_decoration(var_id, DecorationBuiltIn, BuiltInPrimitiveId);
builtin_primitive_id_id = var_id;
mark_implicit_builtin(StorageClassInput, BuiltInPrimitiveId, var_id);
}
}
if (!has_subgroup_invocation_id && (need_subgroup_mask || needs_subgroup_invocation_id))
{
uint32_t offset = ir.increase_bound_by(3);
uint32_t type_id = offset;
uint32_t type_ptr_id = offset + 1;
uint32_t var_id = offset + 2;
// Create gl_SubgroupInvocationID.
SPIRType uint_type;
uint_type.basetype = SPIRType::UInt;
uint_type.width = 32;
set<SPIRType>(type_id, uint_type);
SPIRType uint_type_ptr;
uint_type_ptr = uint_type;
uint_type_ptr.pointer = true;
uint_type_ptr.parent_type = type_id;
uint_type_ptr.storage = StorageClassInput;
auto &ptr_type = set<SPIRType>(type_ptr_id, uint_type_ptr);
ptr_type.self = type_id;
set<SPIRVariable>(var_id, type_ptr_id, StorageClassInput);
set_decoration(var_id, DecorationBuiltIn, BuiltInSubgroupLocalInvocationId);
builtin_subgroup_invocation_id_id = var_id;
mark_implicit_builtin(StorageClassInput, BuiltInSubgroupLocalInvocationId, var_id);
}
if (!has_subgroup_size && need_subgroup_ge_mask)
{
uint32_t offset = ir.increase_bound_by(3);
uint32_t type_id = offset;
uint32_t type_ptr_id = offset + 1;
uint32_t var_id = offset + 2;
// Create gl_SubgroupSize.
SPIRType uint_type;
uint_type.basetype = SPIRType::UInt;
uint_type.width = 32;
set<SPIRType>(type_id, uint_type);
SPIRType uint_type_ptr;
uint_type_ptr = uint_type;
uint_type_ptr.pointer = true;
uint_type_ptr.parent_type = type_id;
uint_type_ptr.storage = StorageClassInput;
auto &ptr_type = set<SPIRType>(type_ptr_id, uint_type_ptr);
ptr_type.self = type_id;
set<SPIRVariable>(var_id, type_ptr_id, StorageClassInput);
set_decoration(var_id, DecorationBuiltIn, BuiltInSubgroupSize);
builtin_subgroup_size_id = var_id;
mark_implicit_builtin(StorageClassInput, BuiltInSubgroupSize, var_id);
}
if (need_dispatch_base)
{
uint32_t var_id;
if (msl_options.supports_msl_version(1, 2))
{
// If we have MSL 1.2, we can (ab)use the [[grid_origin]] builtin
// to convey this information and save a buffer slot.
uint32_t offset = ir.increase_bound_by(1);
var_id = offset;
set<SPIRVariable>(var_id, workgroup_id_type, StorageClassInput);
set_extended_decoration(var_id, SPIRVCrossDecorationBuiltInDispatchBase);
get_entry_point().interface_variables.push_back(var_id);
}
else
{
// Otherwise, we need to fall back to a good ol' fashioned buffer.
uint32_t offset = ir.increase_bound_by(2);
var_id = offset;
uint32_t type_id = offset + 1;
SPIRType var_type = get<SPIRType>(workgroup_id_type);
var_type.storage = StorageClassUniform;
set<SPIRType>(type_id, var_type);
set<SPIRVariable>(var_id, type_id, StorageClassUniform);
// This should never match anything.
set_decoration(var_id, DecorationDescriptorSet, ~(5u));
set_decoration(var_id, DecorationBinding, msl_options.indirect_params_buffer_index);
set_extended_decoration(var_id, SPIRVCrossDecorationResourceIndexPrimary,
msl_options.indirect_params_buffer_index);
}
set_name(var_id, "spvDispatchBase");
builtin_dispatch_base_id = var_id;
}
}
if (needs_swizzle_buffer_def)
{
uint32_t var_id = build_constant_uint_array_pointer();
set_name(var_id, "spvSwizzleConstants");
// This should never match anything.
set_decoration(var_id, DecorationDescriptorSet, kSwizzleBufferBinding);
set_decoration(var_id, DecorationBinding, msl_options.swizzle_buffer_index);
set_extended_decoration(var_id, SPIRVCrossDecorationResourceIndexPrimary, msl_options.swizzle_buffer_index);
swizzle_buffer_id = var_id;
}
if (!buffers_requiring_array_length.empty())
{
uint32_t var_id = build_constant_uint_array_pointer();
set_name(var_id, "spvBufferSizeConstants");
// This should never match anything.
set_decoration(var_id, DecorationDescriptorSet, kBufferSizeBufferBinding);
set_decoration(var_id, DecorationBinding, msl_options.buffer_size_buffer_index);
set_extended_decoration(var_id, SPIRVCrossDecorationResourceIndexPrimary, msl_options.buffer_size_buffer_index);
buffer_size_buffer_id = var_id;
}
if (needs_view_mask_buffer())
{
uint32_t var_id = build_constant_uint_array_pointer();
set_name(var_id, "spvViewMask");
// This should never match anything.
set_decoration(var_id, DecorationDescriptorSet, ~(4u));
set_decoration(var_id, DecorationBinding, msl_options.view_mask_buffer_index);
set_extended_decoration(var_id, SPIRVCrossDecorationResourceIndexPrimary, msl_options.view_mask_buffer_index);
view_mask_buffer_id = var_id;
}
}
void CompilerMSL::mark_implicit_builtin(StorageClass storage, BuiltIn builtin, uint32_t id)
{
Bitset *active_builtins = nullptr;
switch (storage)
{
case StorageClassInput:
active_builtins = &active_input_builtins;
break;
case StorageClassOutput:
active_builtins = &active_output_builtins;
break;
default:
break;
}
assert(active_builtins != nullptr);
active_builtins->set(builtin);
get_entry_point().interface_variables.push_back(id);
}
uint32_t CompilerMSL::build_constant_uint_array_pointer()
{
uint32_t offset = ir.increase_bound_by(4);
uint32_t type_id = offset;
uint32_t type_ptr_id = offset + 1;
uint32_t type_ptr_ptr_id = offset + 2;
uint32_t var_id = offset + 3;
// Create a buffer to hold extra data, including the swizzle constants.
SPIRType uint_type;
uint_type.basetype = SPIRType::UInt;
uint_type.width = 32;
set<SPIRType>(type_id, uint_type);
SPIRType uint_type_pointer = uint_type;
uint_type_pointer.pointer = true;
uint_type_pointer.pointer_depth = 1;
uint_type_pointer.parent_type = type_id;
uint_type_pointer.storage = StorageClassUniform;
set<SPIRType>(type_ptr_id, uint_type_pointer);
set_decoration(type_ptr_id, DecorationArrayStride, 4);
SPIRType uint_type_pointer2 = uint_type_pointer;
uint_type_pointer2.pointer_depth++;
uint_type_pointer2.parent_type = type_ptr_id;
set<SPIRType>(type_ptr_ptr_id, uint_type_pointer2);
set<SPIRVariable>(var_id, type_ptr_ptr_id, StorageClassUniformConstant);
return var_id;
}
static string create_sampler_address(const char *prefix, MSLSamplerAddress addr)
{
switch (addr)
{
case MSL_SAMPLER_ADDRESS_CLAMP_TO_EDGE:
return join(prefix, "address::clamp_to_edge");
case MSL_SAMPLER_ADDRESS_CLAMP_TO_ZERO:
return join(prefix, "address::clamp_to_zero");
case MSL_SAMPLER_ADDRESS_CLAMP_TO_BORDER:
return join(prefix, "address::clamp_to_border");
case MSL_SAMPLER_ADDRESS_REPEAT:
return join(prefix, "address::repeat");
case MSL_SAMPLER_ADDRESS_MIRRORED_REPEAT:
return join(prefix, "address::mirrored_repeat");
default:
SPIRV_CROSS_THROW("Invalid sampler addressing mode.");
}
}
SPIRType &CompilerMSL::get_stage_in_struct_type()
{
auto &si_var = get<SPIRVariable>(stage_in_var_id);
return get_variable_data_type(si_var);
}
SPIRType &CompilerMSL::get_stage_out_struct_type()
{
auto &so_var = get<SPIRVariable>(stage_out_var_id);
return get_variable_data_type(so_var);
}
SPIRType &CompilerMSL::get_patch_stage_in_struct_type()
{
auto &si_var = get<SPIRVariable>(patch_stage_in_var_id);
return get_variable_data_type(si_var);
}
SPIRType &CompilerMSL::get_patch_stage_out_struct_type()
{
auto &so_var = get<SPIRVariable>(patch_stage_out_var_id);
return get_variable_data_type(so_var);
}
std::string CompilerMSL::get_tess_factor_struct_name()
{
if (get_entry_point().flags.get(ExecutionModeTriangles))
return "MTLTriangleTessellationFactorsHalf";
return "MTLQuadTessellationFactorsHalf";
}
void CompilerMSL::emit_entry_point_declarations()
{
// FIXME: Get test coverage here ...
// Emit constexpr samplers here.
for (auto &samp : constexpr_samplers_by_id)
{
auto &var = get<SPIRVariable>(samp.first);
auto &type = get<SPIRType>(var.basetype);
if (type.basetype == SPIRType::Sampler)
add_resource_name(samp.first);
SmallVector<string> args;
auto &s = samp.second;
if (s.coord != MSL_SAMPLER_COORD_NORMALIZED)
args.push_back("coord::pixel");
if (s.min_filter == s.mag_filter)
{
if (s.min_filter != MSL_SAMPLER_FILTER_NEAREST)
args.push_back("filter::linear");
}
else
{
if (s.min_filter != MSL_SAMPLER_FILTER_NEAREST)
args.push_back("min_filter::linear");
if (s.mag_filter != MSL_SAMPLER_FILTER_NEAREST)
args.push_back("mag_filter::linear");
}
switch (s.mip_filter)
{
case MSL_SAMPLER_MIP_FILTER_NONE:
// Default
break;
case MSL_SAMPLER_MIP_FILTER_NEAREST:
args.push_back("mip_filter::nearest");
break;
case MSL_SAMPLER_MIP_FILTER_LINEAR:
args.push_back("mip_filter::linear");
break;
default:
SPIRV_CROSS_THROW("Invalid mip filter.");
}
if (s.s_address == s.t_address && s.s_address == s.r_address)
{
if (s.s_address != MSL_SAMPLER_ADDRESS_CLAMP_TO_EDGE)
args.push_back(create_sampler_address("", s.s_address));
}
else
{
if (s.s_address != MSL_SAMPLER_ADDRESS_CLAMP_TO_EDGE)
args.push_back(create_sampler_address("s_", s.s_address));
if (s.t_address != MSL_SAMPLER_ADDRESS_CLAMP_TO_EDGE)
args.push_back(create_sampler_address("t_", s.t_address));
if (s.r_address != MSL_SAMPLER_ADDRESS_CLAMP_TO_EDGE)
args.push_back(create_sampler_address("r_", s.r_address));
}
if (s.compare_enable)
{
switch (s.compare_func)
{
case MSL_SAMPLER_COMPARE_FUNC_ALWAYS:
args.push_back("compare_func::always");
break;
case MSL_SAMPLER_COMPARE_FUNC_NEVER:
args.push_back("compare_func::never");
break;
case MSL_SAMPLER_COMPARE_FUNC_EQUAL:
args.push_back("compare_func::equal");
break;
case MSL_SAMPLER_COMPARE_FUNC_NOT_EQUAL:
args.push_back("compare_func::not_equal");
break;
case MSL_SAMPLER_COMPARE_FUNC_LESS:
args.push_back("compare_func::less");
break;
case MSL_SAMPLER_COMPARE_FUNC_LESS_EQUAL:
args.push_back("compare_func::less_equal");
break;
case MSL_SAMPLER_COMPARE_FUNC_GREATER:
args.push_back("compare_func::greater");
break;
case MSL_SAMPLER_COMPARE_FUNC_GREATER_EQUAL:
args.push_back("compare_func::greater_equal");
break;
default:
SPIRV_CROSS_THROW("Invalid sampler compare function.");
}
}
if (s.s_address == MSL_SAMPLER_ADDRESS_CLAMP_TO_BORDER || s.t_address == MSL_SAMPLER_ADDRESS_CLAMP_TO_BORDER ||
s.r_address == MSL_SAMPLER_ADDRESS_CLAMP_TO_BORDER)
{
switch (s.border_color)
{
case MSL_SAMPLER_BORDER_COLOR_OPAQUE_BLACK:
args.push_back("border_color::opaque_black");
break;
case MSL_SAMPLER_BORDER_COLOR_OPAQUE_WHITE:
args.push_back("border_color::opaque_white");
break;
case MSL_SAMPLER_BORDER_COLOR_TRANSPARENT_BLACK:
args.push_back("border_color::transparent_black");
break;
default:
SPIRV_CROSS_THROW("Invalid sampler border color.");
}
}
if (s.anisotropy_enable)
args.push_back(join("max_anisotropy(", s.max_anisotropy, ")"));
if (s.lod_clamp_enable)
{
args.push_back(join("lod_clamp(", convert_to_string(s.lod_clamp_min, current_locale_radix_character), ", ",
convert_to_string(s.lod_clamp_max, current_locale_radix_character), ")"));
}
statement("constexpr sampler ",
type.basetype == SPIRType::SampledImage ? to_sampler_expression(samp.first) : to_name(samp.first),
"(", merge(args), ");");
}
// Emit buffer arrays here.
for (uint32_t array_id : buffer_arrays)
{
const auto &var = get<SPIRVariable>(array_id);
const auto &type = get_variable_data_type(var);
string name = to_name(array_id);
statement(get_argument_address_space(var), " ", type_to_glsl(type), "* ", to_restrict(array_id), name, "[] =");
begin_scope();
for (uint32_t i = 0; i < type.array[0]; ++i)
statement(name + "_" + convert_to_string(i) + ",");
end_scope_decl();
statement_no_indent("");
}
// For some reason, without this, we end up emitting the arrays twice.
buffer_arrays.clear();
}
string CompilerMSL::compile()
{
// Do not deal with GLES-isms like precision, older extensions and such.
options.vulkan_semantics = true;
options.es = false;
options.version = 450;
backend.null_pointer_literal = "nullptr";
backend.float_literal_suffix = false;
backend.uint32_t_literal_suffix = true;
backend.int16_t_literal_suffix = "";
backend.uint16_t_literal_suffix = "u";
backend.basic_int_type = "int";
backend.basic_uint_type = "uint";
backend.basic_int8_type = "char";
backend.basic_uint8_type = "uchar";
backend.basic_int16_type = "short";
backend.basic_uint16_type = "ushort";
backend.discard_literal = "discard_fragment()";
backend.demote_literal = "unsupported-demote";
backend.boolean_mix_function = "select";
backend.swizzle_is_function = false;
backend.shared_is_implied = false;
backend.use_initializer_list = true;
backend.use_typed_initializer_list = true;
backend.native_row_major_matrix = false;
backend.unsized_array_supported = false;
backend.can_declare_arrays_inline = false;
backend.can_return_array = false;
backend.allow_truncated_access_chain = true;
backend.array_is_value_type = false;
backend.comparison_image_samples_scalar = true;
backend.native_pointers = true;
backend.nonuniform_qualifier = "";
backend.support_small_type_sampling_result = true;
capture_output_to_buffer = msl_options.capture_output_to_buffer;
is_rasterization_disabled = msl_options.disable_rasterization || capture_output_to_buffer;
// Initialize array here rather than constructor, MSVC 2013 workaround.
for (auto &id : next_metal_resource_ids)
id = 0;
fixup_type_alias();
replace_illegal_names();
build_function_control_flow_graphs_and_analyze();
update_active_builtins();
analyze_image_and_sampler_usage();
analyze_sampled_image_usage();
preprocess_op_codes();
build_implicit_builtins();
fixup_image_load_store_access();
set_enabled_interface_variables(get_active_interface_variables());
if (swizzle_buffer_id)
active_interface_variables.insert(swizzle_buffer_id);
if (buffer_size_buffer_id)
active_interface_variables.insert(buffer_size_buffer_id);
if (view_mask_buffer_id)
active_interface_variables.insert(view_mask_buffer_id);
if (builtin_layer_id)
active_interface_variables.insert(builtin_layer_id);
if (builtin_dispatch_base_id && !msl_options.supports_msl_version(1, 2))
active_interface_variables.insert(builtin_dispatch_base_id);
// Create structs to hold input, output and uniform variables.
// Do output first to ensure out. is declared at top of entry function.
qual_pos_var_name = "";
stage_out_var_id = add_interface_block(StorageClassOutput);
patch_stage_out_var_id = add_interface_block(StorageClassOutput, true);
stage_in_var_id = add_interface_block(StorageClassInput);
if (get_execution_model() == ExecutionModelTessellationEvaluation)
patch_stage_in_var_id = add_interface_block(StorageClassInput, true);
if (get_execution_model() == ExecutionModelTessellationControl)
stage_out_ptr_var_id = add_interface_block_pointer(stage_out_var_id, StorageClassOutput);
if (is_tessellation_shader())
stage_in_ptr_var_id = add_interface_block_pointer(stage_in_var_id, StorageClassInput);
// Metal vertex functions that define no output must disable rasterization and return void.
if (!stage_out_var_id)
is_rasterization_disabled = true;
// Convert the use of global variables to recursively-passed function parameters
localize_global_variables();
extract_global_variables_from_functions();
// Mark any non-stage-in structs to be tightly packed.
mark_packable_structs();
reorder_type_alias();
// Add fixup hooks required by shader inputs and outputs. This needs to happen before
// 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
{
if (pass_count >= 3)
SPIRV_CROSS_THROW("Over 3 compilation loops detected. Must be a bug!");
reset();
// Start bindings at zero.
next_metal_resource_index_buffer = 0;
next_metal_resource_index_texture = 0;
next_metal_resource_index_sampler = 0;
for (auto &id : next_metal_resource_ids)
id = 0;
// Move constructor for this type is broken on GCC 4.9 ...
buffer.reset();
emit_header();
emit_specialization_constants_and_structs();
emit_resources();
emit_custom_functions();
emit_function(get<SPIRFunction>(ir.default_entry_point), Bitset());
pass_count++;
} while (is_forcing_recompilation());
return buffer.str();
}
// Register the need to output any custom functions.
void CompilerMSL::preprocess_op_codes()
{
OpCodePreprocessor preproc(*this);
traverse_all_reachable_opcodes(get<SPIRFunction>(ir.default_entry_point), preproc);
suppress_missing_prototypes = preproc.suppress_missing_prototypes;
if (preproc.uses_atomics)
{
add_header_line("#include <metal_atomic>");
add_pragma_line("#pragma clang diagnostic ignored \"-Wunused-variable\"");
}
// Metal vertex functions that write to resources must disable rasterization and return void.
if (preproc.uses_resource_write)
is_rasterization_disabled = true;
// Tessellation control shaders are run as compute functions in Metal, and so
// must capture their output to a buffer.
if (get_execution_model() == ExecutionModelTessellationControl)
{
is_rasterization_disabled = true;
capture_output_to_buffer = true;
}
if (preproc.needs_subgroup_invocation_id)
needs_subgroup_invocation_id = true;
}
// Move the Private and Workgroup global variables to the entry function.
// Non-constant variables cannot have global scope in Metal.
void CompilerMSL::localize_global_variables()
{
auto &entry_func = get<SPIRFunction>(ir.default_entry_point);
auto iter = global_variables.begin();
while (iter != global_variables.end())
{
uint32_t v_id = *iter;
auto &var = get<SPIRVariable>(v_id);
if (var.storage == StorageClassPrivate || var.storage == StorageClassWorkgroup)
{
if (!variable_is_lut(var))
entry_func.add_local_variable(v_id);
iter = global_variables.erase(iter);
}
else
iter++;
}
}
// For any global variable accessed directly by a function,
// extract that variable and add it as an argument to that function.
void CompilerMSL::extract_global_variables_from_functions()
{
// Uniforms
unordered_set<uint32_t> global_var_ids;
ir.for_each_typed_id<SPIRVariable>([&](uint32_t, SPIRVariable &var) {
if (var.storage == StorageClassInput || var.storage == StorageClassOutput ||
var.storage == StorageClassUniform || var.storage == StorageClassUniformConstant ||
var.storage == StorageClassPushConstant || var.storage == StorageClassStorageBuffer)
{
global_var_ids.insert(var.self);
}
});
// Local vars that are declared in the main function and accessed directly by a function
auto &entry_func = get<SPIRFunction>(ir.default_entry_point);
for (auto &var : entry_func.local_variables)
if (get<SPIRVariable>(var).storage != StorageClassFunction)
global_var_ids.insert(var);
std::set<uint32_t> added_arg_ids;
unordered_set<uint32_t> processed_func_ids;
extract_global_variables_from_function(ir.default_entry_point, added_arg_ids, global_var_ids, processed_func_ids);
}
// MSL does not support the use of global variables for shader input content.
// For any global variable accessed directly by the specified function, extract that variable,
// add it as an argument to that function, and the arg to the added_arg_ids collection.
void CompilerMSL::extract_global_variables_from_function(uint32_t func_id, std::set<uint32_t> &added_arg_ids,
unordered_set<uint32_t> &global_var_ids,
unordered_set<uint32_t> &processed_func_ids)
{
// Avoid processing a function more than once
if (processed_func_ids.find(func_id) != processed_func_ids.end())
{
// Return function global variables
added_arg_ids = function_global_vars[func_id];
return;
}
processed_func_ids.insert(func_id);
auto &func = get<SPIRFunction>(func_id);
// Recursively establish global args added to functions on which we depend.
for (auto block : func.blocks)
{
auto &b = get<SPIRBlock>(block);
for (auto &i : b.ops)
{
auto ops = stream(i);
auto op = static_cast<Op>(i.op);
switch (op)
{
case OpLoad:
case OpInBoundsAccessChain:
case OpAccessChain:
case OpPtrAccessChain:
case OpArrayLength:
{
uint32_t base_id = ops[2];
if (global_var_ids.find(base_id) != global_var_ids.end())
added_arg_ids.insert(base_id);
auto &type = get<SPIRType>(ops[0]);
if (type.basetype == SPIRType::Image && type.image.dim == DimSubpassData)
{
// Implicitly reads gl_FragCoord.
assert(builtin_frag_coord_id != 0);
added_arg_ids.insert(builtin_frag_coord_id);
}
break;
}
case OpFunctionCall:
{
// First see if any of the function call args are globals
for (uint32_t arg_idx = 3; arg_idx < i.length; arg_idx++)
{
uint32_t arg_id = ops[arg_idx];
if (global_var_ids.find(arg_id) != global_var_ids.end())
added_arg_ids.insert(arg_id);
}
// Then recurse into the function itself to extract globals used internally in the function
uint32_t inner_func_id = ops[2];
std::set<uint32_t> inner_func_args;
extract_global_variables_from_function(inner_func_id, inner_func_args, global_var_ids,
processed_func_ids);
added_arg_ids.insert(inner_func_args.begin(), inner_func_args.end());
break;
}
case OpStore:
{
uint32_t base_id = ops[0];
if (global_var_ids.find(base_id) != global_var_ids.end())
added_arg_ids.insert(base_id);
break;
}
case OpSelect:
{
uint32_t base_id = ops[3];
if (global_var_ids.find(base_id) != global_var_ids.end())
added_arg_ids.insert(base_id);
base_id = ops[4];
if (global_var_ids.find(base_id) != global_var_ids.end())
added_arg_ids.insert(base_id);
break;
}
default:
break;
}
// TODO: Add all other operations which can affect memory.
// We should consider a more unified system here to reduce boiler-plate.
// This kind of analysis is done in several places ...
}
}
function_global_vars[func_id] = added_arg_ids;
// Add the global variables as arguments to the function
if (func_id != ir.default_entry_point)
{
bool added_in = false;
bool added_out = false;
for (uint32_t arg_id : added_arg_ids)
{
auto &var = get<SPIRVariable>(arg_id);
uint32_t type_id = var.basetype;
auto *p_type = &get<SPIRType>(type_id);
BuiltIn bi_type = BuiltIn(get_decoration(arg_id, DecorationBuiltIn));
if (((is_tessellation_shader() && var.storage == StorageClassInput) ||
(get_execution_model() == ExecutionModelTessellationControl && var.storage == StorageClassOutput)) &&
!(has_decoration(arg_id, DecorationPatch) || is_patch_block(*p_type)) &&
(!is_builtin_variable(var) || bi_type == BuiltInPosition || bi_type == BuiltInPointSize ||
bi_type == BuiltInClipDistance || bi_type == BuiltInCullDistance ||
p_type->basetype == SPIRType::Struct))
{
// Tessellation control shaders see inputs and per-vertex outputs as arrays.
// Similarly, tessellation evaluation shaders see per-vertex inputs as arrays.
// We collected them into a structure; we must pass the array of this
// structure to the function.
std::string name;
if (var.storage == StorageClassInput)
{
if (added_in)
continue;
name = input_wg_var_name;
arg_id = stage_in_ptr_var_id;
added_in = true;
}
else if (var.storage == StorageClassOutput)
{
if (added_out)
continue;
name = "gl_out";
arg_id = stage_out_ptr_var_id;
added_out = true;
}
type_id = get<SPIRVariable>(arg_id).basetype;
uint32_t next_id = ir.increase_bound_by(1);
func.add_parameter(type_id, next_id, true);
set<SPIRVariable>(next_id, type_id, StorageClassFunction, 0, arg_id);
set_name(next_id, name);
}
else if (is_builtin_variable(var) && p_type->basetype == SPIRType::Struct)
{
// Get the pointee type
type_id = get_pointee_type_id(type_id);
p_type = &get<SPIRType>(type_id);
uint32_t mbr_idx = 0;
for (auto &mbr_type_id : p_type->member_types)
{
BuiltIn builtin = BuiltInMax;
bool is_builtin = is_member_builtin(*p_type, mbr_idx, &builtin);
if (is_builtin && has_active_builtin(builtin, var.storage))
{
// Add a arg variable with the same type and decorations as the member
uint32_t next_ids = ir.increase_bound_by(2);
uint32_t ptr_type_id = next_ids + 0;
uint32_t var_id = next_ids + 1;
// Make sure we have an actual pointer type,
// so that we will get the appropriate address space when declaring these builtins.
auto &ptr = set<SPIRType>(ptr_type_id, get<SPIRType>(mbr_type_id));
ptr.self = mbr_type_id;
ptr.storage = var.storage;
ptr.pointer = true;
ptr.parent_type = mbr_type_id;
func.add_parameter(mbr_type_id, var_id, true);
set<SPIRVariable>(var_id, ptr_type_id, StorageClassFunction);
ir.meta[var_id].decoration = ir.meta[type_id].members[mbr_idx];
}
mbr_idx++;
}
}
else
{
uint32_t next_id = ir.increase_bound_by(1);
func.add_parameter(type_id, next_id, true);
set<SPIRVariable>(next_id, type_id, StorageClassFunction, 0, arg_id);
// Ensure the existing variable has a valid name and the new variable has all the same meta info
set_name(arg_id, ensure_valid_name(to_name(arg_id), "v"));
ir.meta[next_id] = ir.meta[arg_id];
}
}
}
}
// For all variables that are some form of non-input-output interface block, mark that all the structs
// that are recursively contained within the type referenced by that variable should be packed tightly.
void CompilerMSL::mark_packable_structs()
{
ir.for_each_typed_id<SPIRVariable>([&](uint32_t, SPIRVariable &var) {
if (var.storage != StorageClassFunction && !is_hidden_variable(var))
{
auto &type = this->get<SPIRType>(var.basetype);
if (type.pointer &&
(type.storage == StorageClassUniform || type.storage == StorageClassUniformConstant ||
type.storage == StorageClassPushConstant || type.storage == StorageClassStorageBuffer) &&
(has_decoration(type.self, DecorationBlock) || has_decoration(type.self, DecorationBufferBlock)))
mark_as_packable(type);
}
});
}
// If the specified type is a struct, it and any nested structs
// are marked as packable with the SPIRVCrossDecorationBufferBlockRepacked decoration,
void CompilerMSL::mark_as_packable(SPIRType &type)
{
// If this is not the base type (eg. it's a pointer or array), tunnel down
if (type.parent_type)
{
mark_as_packable(get<SPIRType>(type.parent_type));
return;
}
if (type.basetype == SPIRType::Struct)
{
set_extended_decoration(type.self, SPIRVCrossDecorationBufferBlockRepacked);
// Recurse
uint32_t mbr_cnt = uint32_t(type.member_types.size());
for (uint32_t mbr_idx = 0; mbr_idx < mbr_cnt; mbr_idx++)
{
uint32_t mbr_type_id = type.member_types[mbr_idx];
auto &mbr_type = get<SPIRType>(mbr_type_id);
mark_as_packable(mbr_type);
if (mbr_type.type_alias)
{
auto &mbr_type_alias = get<SPIRType>(mbr_type.type_alias);
mark_as_packable(mbr_type_alias);
}
}
}
}
// If a vertex attribute exists at the location, it is marked as being used by this shader
void CompilerMSL::mark_location_as_used_by_shader(uint32_t location, StorageClass storage)
{
if ((get_execution_model() == ExecutionModelVertex || is_tessellation_shader()) && (storage == StorageClassInput))
vtx_attrs_in_use.insert(location);
}
uint32_t CompilerMSL::get_target_components_for_fragment_location(uint32_t location) const
{
auto itr = fragment_output_components.find(location);
if (itr == end(fragment_output_components))
return 4;
else
return itr->second;
}
uint32_t CompilerMSL::build_extended_vector_type(uint32_t type_id, uint32_t components)
{
uint32_t new_type_id = ir.increase_bound_by(1);
auto &type = set<SPIRType>(new_type_id, get<SPIRType>(type_id));
type.vecsize = components;
type.self = new_type_id;
type.parent_type = type_id;
type.pointer = false;
return new_type_id;
}
void CompilerMSL::add_plain_variable_to_interface_block(StorageClass storage, const string &ib_var_ref,
SPIRType &ib_type, SPIRVariable &var, bool strip_array)
{
bool is_builtin = is_builtin_variable(var);
BuiltIn builtin = BuiltIn(get_decoration(var.self, DecorationBuiltIn));
bool is_flat = has_decoration(var.self, DecorationFlat);
bool is_noperspective = has_decoration(var.self, DecorationNoPerspective);
bool is_centroid = has_decoration(var.self, DecorationCentroid);
bool is_sample = has_decoration(var.self, DecorationSample);
// Add a reference to the variable type to the interface struct.
uint32_t ib_mbr_idx = uint32_t(ib_type.member_types.size());
uint32_t type_id = ensure_correct_builtin_type(var.basetype, builtin);
var.basetype = type_id;
type_id = get_pointee_type_id(var.basetype);
if (strip_array && is_array(get<SPIRType>(type_id)))
type_id = get<SPIRType>(type_id).parent_type;
auto &type = get<SPIRType>(type_id);
uint32_t target_components = 0;
uint32_t type_components = type.vecsize;
bool padded_output = false;
// Check if we need to pad fragment output to match a certain number of components.
if (get_decoration_bitset(var.self).get(DecorationLocation) && msl_options.pad_fragment_output_components &&
get_entry_point().model == ExecutionModelFragment && storage == StorageClassOutput)
{
uint32_t locn = get_decoration(var.self, DecorationLocation);
target_components = get_target_components_for_fragment_location(locn);
if (type_components < target_components)
{
// Make a new type here.
type_id = build_extended_vector_type(type_id, target_components);
padded_output = true;
}
}
ib_type.member_types.push_back(type_id);
// Give the member a name
string mbr_name = ensure_valid_name(to_expression(var.self), "m");
set_member_name(ib_type.self, ib_mbr_idx, mbr_name);
// Update the original variable reference to include the structure reference
string qual_var_name = ib_var_ref + "." + mbr_name;
auto &entry_func = get<SPIRFunction>(ir.default_entry_point);
if (padded_output)
{
entry_func.add_local_variable(var.self);
vars_needing_early_declaration.push_back(var.self);
entry_func.fixup_hooks_out.push_back([=, &var]() {
SPIRType &padded_type = this->get<SPIRType>(type_id);
statement(qual_var_name, " = ", remap_swizzle(padded_type, type_components, to_name(var.self)), ";");
});
}
else if (!strip_array)
ir.meta[var.self].decoration.qualified_alias = qual_var_name;
if (var.storage == StorageClassOutput && var.initializer != 0)
{
entry_func.fixup_hooks_in.push_back(
[=, &var]() { statement(qual_var_name, " = ", to_expression(var.initializer), ";"); });
}
// Copy the variable location from the original variable to the member
if (get_decoration_bitset(var.self).get(DecorationLocation))
{
uint32_t locn = get_decoration(var.self, DecorationLocation);
if (storage == StorageClassInput && (get_execution_model() == ExecutionModelVertex || is_tessellation_shader()))
{
type_id = ensure_correct_attribute_type(var.basetype, locn);
var.basetype = type_id;
type_id = get_pointee_type_id(type_id);
if (strip_array && is_array(get<SPIRType>(type_id)))
type_id = get<SPIRType>(type_id).parent_type;
ib_type.member_types[ib_mbr_idx] = type_id;
}
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
mark_location_as_used_by_shader(locn, storage);
}
else if (is_builtin && is_tessellation_shader() && vtx_attrs_by_builtin.count(builtin))
{
uint32_t locn = vtx_attrs_by_builtin[builtin].location;
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
mark_location_as_used_by_shader(locn, storage);
}
if (get_decoration_bitset(var.self).get(DecorationComponent))
{
uint32_t comp = get_decoration(var.self, DecorationComponent);
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationComponent, comp);
}
if (get_decoration_bitset(var.self).get(DecorationIndex))
{
uint32_t index = get_decoration(var.self, DecorationIndex);
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationIndex, index);
}
// Mark the member as builtin if needed
if (is_builtin)
{
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationBuiltIn, builtin);
if (builtin == BuiltInPosition && storage == StorageClassOutput)
qual_pos_var_name = qual_var_name;
}
// Copy interpolation decorations if needed
if (is_flat)
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationFlat);
if (is_noperspective)
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationNoPerspective);
if (is_centroid)
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationCentroid);
if (is_sample)
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationSample);
set_extended_member_decoration(ib_type.self, ib_mbr_idx, SPIRVCrossDecorationInterfaceOrigID, var.self);
}
void CompilerMSL::add_composite_variable_to_interface_block(StorageClass storage, const string &ib_var_ref,
SPIRType &ib_type, SPIRVariable &var, bool strip_array)
{
auto &entry_func = get<SPIRFunction>(ir.default_entry_point);
auto &var_type = strip_array ? get_variable_element_type(var) : get_variable_data_type(var);
uint32_t elem_cnt = 0;
if (is_matrix(var_type))
{
if (is_array(var_type))
SPIRV_CROSS_THROW("MSL cannot emit arrays-of-matrices in input and output variables.");
elem_cnt = var_type.columns;
}
else if (is_array(var_type))
{
if (var_type.array.size() != 1)
SPIRV_CROSS_THROW("MSL cannot emit arrays-of-arrays in input and output variables.");
elem_cnt = to_array_size_literal(var_type);
}
bool is_builtin = is_builtin_variable(var);
BuiltIn builtin = BuiltIn(get_decoration(var.self, DecorationBuiltIn));
bool is_flat = has_decoration(var.self, DecorationFlat);
bool is_noperspective = has_decoration(var.self, DecorationNoPerspective);
bool is_centroid = has_decoration(var.self, DecorationCentroid);
bool is_sample = has_decoration(var.self, DecorationSample);
auto *usable_type = &var_type;
if (usable_type->pointer)
usable_type = &get<SPIRType>(usable_type->parent_type);
while (is_array(*usable_type) || is_matrix(*usable_type))
usable_type = &get<SPIRType>(usable_type->parent_type);
// If a builtin, force it to have the proper name.
if (is_builtin)
set_name(var.self, builtin_to_glsl(builtin, StorageClassFunction));
entry_func.add_local_variable(var.self);
// We need to declare the variable early and at entry-point scope.
vars_needing_early_declaration.push_back(var.self);
for (uint32_t i = 0; i < elem_cnt; i++)
{
// Add a reference to the variable type to the interface struct.
uint32_t ib_mbr_idx = uint32_t(ib_type.member_types.size());
uint32_t target_components = 0;
bool padded_output = false;
uint32_t type_id = usable_type->self;
// Check if we need to pad fragment output to match a certain number of components.
if (get_decoration_bitset(var.self).get(DecorationLocation) && msl_options.pad_fragment_output_components &&
get_entry_point().model == ExecutionModelFragment && storage == StorageClassOutput)
{
uint32_t locn = get_decoration(var.self, DecorationLocation) + i;
target_components = get_target_components_for_fragment_location(locn);
if (usable_type->vecsize < target_components)
{
// Make a new type here.
type_id = build_extended_vector_type(usable_type->self, target_components);
padded_output = true;
}
}
ib_type.member_types.push_back(get_pointee_type_id(type_id));
// Give the member a name
string mbr_name = ensure_valid_name(join(to_expression(var.self), "_", i), "m");
set_member_name(ib_type.self, ib_mbr_idx, mbr_name);
// There is no qualified alias since we need to flatten the internal array on return.
if (get_decoration_bitset(var.self).get(DecorationLocation))
{
uint32_t locn = get_decoration(var.self, DecorationLocation) + i;
if (storage == StorageClassInput &&
(get_execution_model() == ExecutionModelVertex || is_tessellation_shader()))
{
var.basetype = ensure_correct_attribute_type(var.basetype, locn);
uint32_t mbr_type_id = ensure_correct_attribute_type(usable_type->self, locn);
ib_type.member_types[ib_mbr_idx] = mbr_type_id;
}
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
mark_location_as_used_by_shader(locn, storage);
}
else if (is_builtin && is_tessellation_shader() && vtx_attrs_by_builtin.count(builtin))
{
uint32_t locn = vtx_attrs_by_builtin[builtin].location + i;
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
mark_location_as_used_by_shader(locn, storage);
}
if (get_decoration_bitset(var.self).get(DecorationIndex))
{
uint32_t index = get_decoration(var.self, DecorationIndex);
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationIndex, index);
}
// Copy interpolation decorations if needed
if (is_flat)
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationFlat);
if (is_noperspective)
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationNoPerspective);
if (is_centroid)
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationCentroid);
if (is_sample)
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationSample);
set_extended_member_decoration(ib_type.self, ib_mbr_idx, SPIRVCrossDecorationInterfaceOrigID, var.self);
if (!strip_array)
{
switch (storage)
{
case StorageClassInput:
entry_func.fixup_hooks_in.push_back(
[=, &var]() { statement(to_name(var.self), "[", i, "] = ", ib_var_ref, ".", mbr_name, ";"); });
break;
case StorageClassOutput:
entry_func.fixup_hooks_out.push_back([=, &var]() {
if (padded_output)
{
auto &padded_type = this->get<SPIRType>(type_id);
statement(
ib_var_ref, ".", mbr_name, " = ",
remap_swizzle(padded_type, usable_type->vecsize, join(to_name(var.self), "[", i, "]")),
";");
}
else
statement(ib_var_ref, ".", mbr_name, " = ", to_name(var.self), "[", i, "];");
});
break;
default:
break;
}
}
}
}
uint32_t CompilerMSL::get_accumulated_member_location(const SPIRVariable &var, uint32_t mbr_idx, bool strip_array)
{
auto &type = strip_array ? get_variable_element_type(var) : get_variable_data_type(var);
uint32_t location = get_decoration(var.self, DecorationLocation);
for (uint32_t i = 0; i < mbr_idx; i++)
{
auto &mbr_type = get<SPIRType>(type.member_types[i]);
// Start counting from any place we have a new location decoration.
if (has_member_decoration(type.self, mbr_idx, DecorationLocation))
location = get_member_decoration(type.self, mbr_idx, DecorationLocation);
uint32_t location_count = 1;
if (mbr_type.columns > 1)
location_count = mbr_type.columns;
if (!mbr_type.array.empty())
for (uint32_t j = 0; j < uint32_t(mbr_type.array.size()); j++)
location_count *= to_array_size_literal(mbr_type, j);
location += location_count;
}
return location;
}
void CompilerMSL::add_composite_member_variable_to_interface_block(StorageClass storage, const string &ib_var_ref,
SPIRType &ib_type, SPIRVariable &var,
uint32_t mbr_idx, bool strip_array)
{
auto &entry_func = get<SPIRFunction>(ir.default_entry_point);
auto &var_type = strip_array ? get_variable_element_type(var) : get_variable_data_type(var);
BuiltIn builtin;
bool is_builtin = is_member_builtin(var_type, mbr_idx, &builtin);
bool is_flat =
has_member_decoration(var_type.self, mbr_idx, DecorationFlat) || has_decoration(var.self, DecorationFlat);
bool is_noperspective = has_member_decoration(var_type.self, mbr_idx, DecorationNoPerspective) ||
has_decoration(var.self, DecorationNoPerspective);
bool is_centroid = has_member_decoration(var_type.self, mbr_idx, DecorationCentroid) ||
has_decoration(var.self, DecorationCentroid);
bool is_sample =
has_member_decoration(var_type.self, mbr_idx, DecorationSample) || has_decoration(var.self, DecorationSample);
uint32_t mbr_type_id = var_type.member_types[mbr_idx];
auto &mbr_type = get<SPIRType>(mbr_type_id);
uint32_t elem_cnt = 0;
if (is_matrix(mbr_type))
{
if (is_array(mbr_type))
SPIRV_CROSS_THROW("MSL cannot emit arrays-of-matrices in input and output variables.");
elem_cnt = mbr_type.columns;
}
else if (is_array(mbr_type))
{
if (mbr_type.array.size() != 1)
SPIRV_CROSS_THROW("MSL cannot emit arrays-of-arrays in input and output variables.");
elem_cnt = to_array_size_literal(mbr_type);
}
auto *usable_type = &mbr_type;
if (usable_type->pointer)
usable_type = &get<SPIRType>(usable_type->parent_type);
while (is_array(*usable_type) || is_matrix(*usable_type))
usable_type = &get<SPIRType>(usable_type->parent_type);
for (uint32_t i = 0; i < elem_cnt; i++)
{
// Add a reference to the variable type to the interface struct.
uint32_t ib_mbr_idx = uint32_t(ib_type.member_types.size());
ib_type.member_types.push_back(usable_type->self);
// Give the member a name
string mbr_name = ensure_valid_name(join(to_qualified_member_name(var_type, mbr_idx), "_", i), "m");
set_member_name(ib_type.self, ib_mbr_idx, mbr_name);
if (has_member_decoration(var_type.self, mbr_idx, DecorationLocation))
{
uint32_t locn = get_member_decoration(var_type.self, mbr_idx, DecorationLocation) + i;
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
mark_location_as_used_by_shader(locn, storage);
}
else if (has_decoration(var.self, DecorationLocation))
{
uint32_t locn = get_accumulated_member_location(var, mbr_idx, strip_array) + i;
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
mark_location_as_used_by_shader(locn, storage);
}
else if (is_builtin && is_tessellation_shader() && vtx_attrs_by_builtin.count(builtin))
{
uint32_t locn = vtx_attrs_by_builtin[builtin].location + i;
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
mark_location_as_used_by_shader(locn, storage);
}
if (has_member_decoration(var_type.self, mbr_idx, DecorationComponent))
SPIRV_CROSS_THROW("DecorationComponent on matrices and arrays make little sense.");
// Copy interpolation decorations if needed
if (is_flat)
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationFlat);
if (is_noperspective)
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationNoPerspective);
if (is_centroid)
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationCentroid);
if (is_sample)
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationSample);
set_extended_member_decoration(ib_type.self, ib_mbr_idx, SPIRVCrossDecorationInterfaceOrigID, var.self);
set_extended_member_decoration(ib_type.self, ib_mbr_idx, SPIRVCrossDecorationInterfaceMemberIndex, mbr_idx);
// Unflatten or flatten from [[stage_in]] or [[stage_out]] as appropriate.
if (!strip_array)
{
switch (storage)
{
case StorageClassInput:
entry_func.fixup_hooks_in.push_back([=, &var, &var_type]() {
statement(to_name(var.self), ".", to_member_name(var_type, mbr_idx), "[", i, "] = ", ib_var_ref,
".", mbr_name, ";");
});
break;
case StorageClassOutput:
entry_func.fixup_hooks_out.push_back([=, &var, &var_type]() {
statement(ib_var_ref, ".", mbr_name, " = ", to_name(var.self), ".",
to_member_name(var_type, mbr_idx), "[", i, "];");
});
break;
default:
break;
}
}
}
}
void CompilerMSL::add_plain_member_variable_to_interface_block(StorageClass storage, const string &ib_var_ref,
SPIRType &ib_type, SPIRVariable &var, uint32_t mbr_idx,
bool strip_array)
{
auto &var_type = strip_array ? get_variable_element_type(var) : get_variable_data_type(var);
auto &entry_func = get<SPIRFunction>(ir.default_entry_point);
BuiltIn builtin = BuiltInMax;
bool is_builtin = is_member_builtin(var_type, mbr_idx, &builtin);
bool is_flat =
has_member_decoration(var_type.self, mbr_idx, DecorationFlat) || has_decoration(var.self, DecorationFlat);
bool is_noperspective = has_member_decoration(var_type.self, mbr_idx, DecorationNoPerspective) ||
has_decoration(var.self, DecorationNoPerspective);
bool is_centroid = has_member_decoration(var_type.self, mbr_idx, DecorationCentroid) ||
has_decoration(var.self, DecorationCentroid);
bool is_sample =
has_member_decoration(var_type.self, mbr_idx, DecorationSample) || has_decoration(var.self, DecorationSample);
// Add a reference to the member to the interface struct.
uint32_t mbr_type_id = var_type.member_types[mbr_idx];
uint32_t ib_mbr_idx = uint32_t(ib_type.member_types.size());
mbr_type_id = ensure_correct_builtin_type(mbr_type_id, builtin);
var_type.member_types[mbr_idx] = mbr_type_id;
ib_type.member_types.push_back(mbr_type_id);
// Give the member a name
string mbr_name = ensure_valid_name(to_qualified_member_name(var_type, mbr_idx), "m");
set_member_name(ib_type.self, ib_mbr_idx, mbr_name);
// Update the original variable reference to include the structure reference
string qual_var_name = ib_var_ref + "." + mbr_name;
if (is_builtin && !strip_array)
{
// For the builtin gl_PerVertex, we cannot treat it as a block anyways,
// so redirect to qualified name.
set_member_qualified_name(var_type.self, mbr_idx, qual_var_name);
}
else if (!strip_array)
{
// Unflatten or flatten from [[stage_in]] or [[stage_out]] as appropriate.
switch (storage)
{
case StorageClassInput:
entry_func.fixup_hooks_in.push_back([=, &var, &var_type]() {
statement(to_name(var.self), ".", to_member_name(var_type, mbr_idx), " = ", qual_var_name, ";");
});
break;
case StorageClassOutput:
entry_func.fixup_hooks_out.push_back([=, &var, &var_type]() {
statement(qual_var_name, " = ", to_name(var.self), ".", to_member_name(var_type, mbr_idx), ";");
});
break;
default:
break;
}
}
// Copy the variable location from the original variable to the member
if (has_member_decoration(var_type.self, mbr_idx, DecorationLocation))
{
uint32_t locn = get_member_decoration(var_type.self, mbr_idx, DecorationLocation);
if (storage == StorageClassInput && (get_execution_model() == ExecutionModelVertex || is_tessellation_shader()))
{
mbr_type_id = ensure_correct_attribute_type(mbr_type_id, locn);
var_type.member_types[mbr_idx] = mbr_type_id;
ib_type.member_types[ib_mbr_idx] = mbr_type_id;
}
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
mark_location_as_used_by_shader(locn, storage);
}
else if (has_decoration(var.self, DecorationLocation))
{
// The block itself might have a location and in this case, all members of the block
// receive incrementing locations.
uint32_t locn = get_accumulated_member_location(var, mbr_idx, strip_array);
if (storage == StorageClassInput && (get_execution_model() == ExecutionModelVertex || is_tessellation_shader()))
{
mbr_type_id = ensure_correct_attribute_type(mbr_type_id, locn);
var_type.member_types[mbr_idx] = mbr_type_id;
ib_type.member_types[ib_mbr_idx] = mbr_type_id;
}
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
mark_location_as_used_by_shader(locn, storage);
}
else if (is_builtin && is_tessellation_shader() && vtx_attrs_by_builtin.count(builtin))
{
uint32_t locn = 0;
auto builtin_itr = vtx_attrs_by_builtin.find(builtin);
if (builtin_itr != end(vtx_attrs_by_builtin))
locn = builtin_itr->second.location;
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
mark_location_as_used_by_shader(locn, storage);
}
// Copy the component location, if present.
if (has_member_decoration(var_type.self, mbr_idx, DecorationComponent))
{
uint32_t comp = get_member_decoration(var_type.self, mbr_idx, DecorationComponent);
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationComponent, comp);
}
// Mark the member as builtin if needed
if (is_builtin)
{
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationBuiltIn, builtin);
if (builtin == BuiltInPosition && storage == StorageClassOutput)
qual_pos_var_name = qual_var_name;
}
// Copy interpolation decorations if needed
if (is_flat)
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationFlat);
if (is_noperspective)
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationNoPerspective);
if (is_centroid)
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationCentroid);
if (is_sample)
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationSample);
set_extended_member_decoration(ib_type.self, ib_mbr_idx, SPIRVCrossDecorationInterfaceOrigID, var.self);
set_extended_member_decoration(ib_type.self, ib_mbr_idx, SPIRVCrossDecorationInterfaceMemberIndex, mbr_idx);
}
// In Metal, the tessellation levels are stored as tightly packed half-precision floating point values.
// But, stage-in attribute offsets and strides must be multiples of four, so we can't pass the levels
// individually. Therefore, we must pass them as vectors. Triangles get a single float4, with the outer
// levels in 'xyz' and the inner level in 'w'. Quads get a float4 containing the outer levels and a
// float2 containing the inner levels.
void CompilerMSL::add_tess_level_input_to_interface_block(const std::string &ib_var_ref, SPIRType &ib_type,
SPIRVariable &var)
{
auto &entry_func = get<SPIRFunction>(ir.default_entry_point);
auto &var_type = get_variable_element_type(var);
BuiltIn builtin = BuiltIn(get_decoration(var.self, DecorationBuiltIn));
// Force the variable to have the proper name.
set_name(var.self, builtin_to_glsl(builtin, StorageClassFunction));
if (get_entry_point().flags.get(ExecutionModeTriangles))
{
// Triangles are tricky, because we want only one member in the struct.
// We need to declare the variable early and at entry-point scope.
entry_func.add_local_variable(var.self);
vars_needing_early_declaration.push_back(var.self);
string mbr_name = "gl_TessLevel";
// If we already added the other one, we can skip this step.
if (!added_builtin_tess_level)
{
// Add a reference to the variable type to the interface struct.
uint32_t ib_mbr_idx = uint32_t(ib_type.member_types.size());
uint32_t type_id = build_extended_vector_type(var_type.self, 4);
ib_type.member_types.push_back(type_id);
// Give the member a name
set_member_name(ib_type.self, ib_mbr_idx, mbr_name);
// There is no qualified alias since we need to flatten the internal array on return.
if (get_decoration_bitset(var.self).get(DecorationLocation))
{
uint32_t locn = get_decoration(var.self, DecorationLocation);
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
mark_location_as_used_by_shader(locn, StorageClassInput);
}
else if (vtx_attrs_by_builtin.count(builtin))
{
uint32_t locn = vtx_attrs_by_builtin[builtin].location;
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
mark_location_as_used_by_shader(locn, StorageClassInput);
}
added_builtin_tess_level = true;
}
switch (builtin)
{
case BuiltInTessLevelOuter:
entry_func.fixup_hooks_in.push_back([=, &var]() {
statement(to_name(var.self), "[0] = ", ib_var_ref, ".", mbr_name, ".x;");
statement(to_name(var.self), "[1] = ", ib_var_ref, ".", mbr_name, ".y;");
statement(to_name(var.self), "[2] = ", ib_var_ref, ".", mbr_name, ".z;");
});
break;
case BuiltInTessLevelInner:
entry_func.fixup_hooks_in.push_back(
[=, &var]() { statement(to_name(var.self), "[0] = ", ib_var_ref, ".", mbr_name, ".w;"); });
break;
default:
assert(false);
break;
}
}
else
{
// Add a reference to the variable type to the interface struct.
uint32_t ib_mbr_idx = uint32_t(ib_type.member_types.size());
uint32_t type_id = build_extended_vector_type(var_type.self, builtin == BuiltInTessLevelOuter ? 4 : 2);
// Change the type of the variable, too.
uint32_t ptr_type_id = ir.increase_bound_by(1);
auto &new_var_type = set<SPIRType>(ptr_type_id, get<SPIRType>(type_id));
new_var_type.pointer = true;
new_var_type.storage = StorageClassInput;
new_var_type.parent_type = type_id;
var.basetype = ptr_type_id;
ib_type.member_types.push_back(type_id);
// Give the member a name
string mbr_name = to_expression(var.self);
set_member_name(ib_type.self, ib_mbr_idx, mbr_name);
// Since vectors can be indexed like arrays, there is no need to unpack this. We can
// just refer to the vector directly. So give it a qualified alias.
string qual_var_name = ib_var_ref + "." + mbr_name;
ir.meta[var.self].decoration.qualified_alias = qual_var_name;
if (get_decoration_bitset(var.self).get(DecorationLocation))
{
uint32_t locn = get_decoration(var.self, DecorationLocation);
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
mark_location_as_used_by_shader(locn, StorageClassInput);
}
else if (vtx_attrs_by_builtin.count(builtin))
{
uint32_t locn = vtx_attrs_by_builtin[builtin].location;
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
mark_location_as_used_by_shader(locn, StorageClassInput);
}
}
}
void CompilerMSL::add_variable_to_interface_block(StorageClass storage, const string &ib_var_ref, SPIRType &ib_type,
SPIRVariable &var, bool strip_array)
{
auto &entry_func = get<SPIRFunction>(ir.default_entry_point);
// Tessellation control I/O variables and tessellation evaluation per-point inputs are
// usually declared as arrays. In these cases, we want to add the element type to the
// interface block, since in Metal it's the interface block itself which is arrayed.
auto &var_type = strip_array ? get_variable_element_type(var) : get_variable_data_type(var);
bool is_builtin = is_builtin_variable(var);
auto builtin = BuiltIn(get_decoration(var.self, DecorationBuiltIn));
if (var_type.basetype == SPIRType::Struct)
{
if (!is_builtin_type(var_type) && (!capture_output_to_buffer || storage == StorageClassInput) && !strip_array)
{
// For I/O blocks or structs, we will need to pass the block itself around
// to functions if they are used globally in leaf functions.
// Rather than passing down member by member,
// we unflatten I/O blocks while running the shader,
// and pass the actual struct type down to leaf functions.
// We then unflatten inputs, and flatten outputs in the "fixup" stages.
entry_func.add_local_variable(var.self);
vars_needing_early_declaration.push_back(var.self);
}
if (capture_output_to_buffer && storage != StorageClassInput && !has_decoration(var_type.self, DecorationBlock))
{
// In Metal tessellation shaders, the interface block itself is arrayed. This makes things
// very complicated, since stage-in structures in MSL don't support nested structures.
// Luckily, for stage-out when capturing output, we can avoid this and just add
// composite members directly, because the stage-out structure is stored to a buffer,
// not returned.
add_plain_variable_to_interface_block(storage, ib_var_ref, ib_type, var, strip_array);
}
else
{
// Flatten the struct members into the interface struct
for (uint32_t mbr_idx = 0; mbr_idx < uint32_t(var_type.member_types.size()); mbr_idx++)
{
builtin = BuiltInMax;
is_builtin = is_member_builtin(var_type, mbr_idx, &builtin);
auto &mbr_type = get<SPIRType>(var_type.member_types[mbr_idx]);
if (!is_builtin || has_active_builtin(builtin, storage))
{
if ((!is_builtin ||
(storage == StorageClassInput && get_execution_model() != ExecutionModelFragment)) &&
(storage == StorageClassInput || storage == StorageClassOutput) &&
(is_matrix(mbr_type) || is_array(mbr_type)))
{
add_composite_member_variable_to_interface_block(storage, ib_var_ref, ib_type, var, mbr_idx,
strip_array);
}
else
{
add_plain_member_variable_to_interface_block(storage, ib_var_ref, ib_type, var, mbr_idx,
strip_array);
}
}
}
}
}
else if (get_execution_model() == ExecutionModelTessellationEvaluation && storage == StorageClassInput &&
!strip_array && is_builtin && (builtin == BuiltInTessLevelOuter || builtin == BuiltInTessLevelInner))
{
add_tess_level_input_to_interface_block(ib_var_ref, ib_type, var);
}
else if (var_type.basetype == SPIRType::Boolean || var_type.basetype == SPIRType::Char ||
type_is_integral(var_type) || type_is_floating_point(var_type) || var_type.basetype == SPIRType::Boolean)
{
if (!is_builtin || has_active_builtin(builtin, storage))
{
// MSL does not allow matrices or arrays in input or output variables, so need to handle it specially.
if ((!is_builtin || (storage == StorageClassInput && get_execution_model() != ExecutionModelFragment)) &&
(storage == StorageClassInput || (storage == StorageClassOutput && !capture_output_to_buffer)) &&
(is_matrix(var_type) || is_array(var_type)))
{
add_composite_variable_to_interface_block(storage, ib_var_ref, ib_type, var, strip_array);
}
else
{
add_plain_variable_to_interface_block(storage, ib_var_ref, ib_type, var, strip_array);
}
}
}
}
// Fix up the mapping of variables to interface member indices, which is used to compile access chains
// for per-vertex variables in a tessellation control shader.
void CompilerMSL::fix_up_interface_member_indices(StorageClass storage, uint32_t ib_type_id)
{
// Only needed for tessellation shaders.
if (get_execution_model() != ExecutionModelTessellationControl &&
!(get_execution_model() == ExecutionModelTessellationEvaluation && storage == StorageClassInput))
return;
bool in_array = false;
for (uint32_t i = 0; i < ir.meta[ib_type_id].members.size(); i++)
{
uint32_t var_id = get_extended_member_decoration(ib_type_id, i, SPIRVCrossDecorationInterfaceOrigID);
if (!var_id)
continue;
auto &var = get<SPIRVariable>(var_id);
// Unfortunately, all this complexity is needed to handle flattened structs and/or
// arrays.
if (storage == StorageClassInput)
{
auto &type = get_variable_element_type(var);
if (is_array(type) || is_matrix(type))
{
if (in_array)
continue;
in_array = true;
set_extended_decoration(var_id, SPIRVCrossDecorationInterfaceMemberIndex, i);
}
else
{
if (type.basetype == SPIRType::Struct)
{
uint32_t mbr_idx =
get_extended_member_decoration(ib_type_id, i, SPIRVCrossDecorationInterfaceMemberIndex);
auto &mbr_type = get<SPIRType>(type.member_types[mbr_idx]);
if (is_array(mbr_type) || is_matrix(mbr_type))
{
if (in_array)
continue;
in_array = true;
set_extended_member_decoration(var_id, mbr_idx, SPIRVCrossDecorationInterfaceMemberIndex, i);
}
else
{
in_array = false;
set_extended_member_decoration(var_id, mbr_idx, SPIRVCrossDecorationInterfaceMemberIndex, i);
}
}
else
{
in_array = false;
set_extended_decoration(var_id, SPIRVCrossDecorationInterfaceMemberIndex, i);
}
}
}
else
set_extended_decoration(var_id, SPIRVCrossDecorationInterfaceMemberIndex, i);
}
}
// Add an interface structure for the type of storage, which is either StorageClassInput or StorageClassOutput.
// Returns the ID of the newly added variable, or zero if no variable was added.
uint32_t CompilerMSL::add_interface_block(StorageClass storage, bool patch)
{
// Accumulate the variables that should appear in the interface struct.
SmallVector<SPIRVariable *> vars;
bool incl_builtins = storage == StorageClassOutput || is_tessellation_shader();
bool has_seen_barycentric = false;
ir.for_each_typed_id<SPIRVariable>([&](uint32_t var_id, SPIRVariable &var) {
if (var.storage != storage)
return;
auto &type = this->get<SPIRType>(var.basetype);
bool is_builtin = is_builtin_variable(var);
auto bi_type = BuiltIn(get_decoration(var_id, DecorationBuiltIn));
// These builtins are part of the stage in/out structs.
bool is_interface_block_builtin =
(bi_type == BuiltInPosition || bi_type == BuiltInPointSize || bi_type == BuiltInClipDistance ||
bi_type == BuiltInCullDistance || bi_type == BuiltInLayer || bi_type == BuiltInViewportIndex ||
bi_type == BuiltInBaryCoordNV || bi_type == BuiltInBaryCoordNoPerspNV || bi_type == BuiltInFragDepth ||
bi_type == BuiltInFragStencilRefEXT || bi_type == BuiltInSampleMask) ||
(get_execution_model() == ExecutionModelTessellationEvaluation &&
(bi_type == BuiltInTessLevelOuter || bi_type == BuiltInTessLevelInner));
bool is_active = interface_variable_exists_in_entry_point(var.self);
if (is_builtin && is_active)
{
// Only emit the builtin if it's active in this entry point. Interface variable list might lie.
is_active = has_active_builtin(bi_type, storage);
}
bool filter_patch_decoration = (has_decoration(var_id, DecorationPatch) || is_patch_block(type)) == patch;
bool hidden = is_hidden_variable(var, incl_builtins);
// Barycentric inputs must be emitted in stage-in, because they can have interpolation arguments.
if (is_active && (bi_type == BuiltInBaryCoordNV || bi_type == BuiltInBaryCoordNoPerspNV))
{
if (has_seen_barycentric)
SPIRV_CROSS_THROW("Cannot declare both BaryCoordNV and BaryCoordNoPerspNV in same shader in MSL.");
has_seen_barycentric = true;
hidden = false;
}
if (is_active && !hidden && type.pointer && filter_patch_decoration &&
(!is_builtin || is_interface_block_builtin))
{
vars.push_back(&var);
}
});
// If no variables qualify, leave.
// For patch input in a tessellation evaluation shader, the per-vertex stage inputs
// are included in a special patch control point array.
if (vars.empty() && !(storage == StorageClassInput && patch && stage_in_var_id))
return 0;
// Add a new typed variable for this interface structure.
// The initializer expression is allocated here, but populated when the function
// declaraion is emitted, because it is cleared after each compilation pass.
uint32_t next_id = ir.increase_bound_by(3);
uint32_t ib_type_id = next_id++;
auto &ib_type = set<SPIRType>(ib_type_id);
ib_type.basetype = SPIRType::Struct;
ib_type.storage = storage;
set_decoration(ib_type_id, DecorationBlock);
uint32_t ib_var_id = next_id++;
auto &var = set<SPIRVariable>(ib_var_id, ib_type_id, storage, 0);
var.initializer = next_id++;
string ib_var_ref;
auto &entry_func = get<SPIRFunction>(ir.default_entry_point);
switch (storage)
{
case StorageClassInput:
ib_var_ref = patch ? patch_stage_in_var_name : stage_in_var_name;
if (get_execution_model() == ExecutionModelTessellationControl)
{
// Add a hook to populate the shared workgroup memory containing
// the gl_in array.
entry_func.fixup_hooks_in.push_back([=]() {
// Can't use PatchVertices yet; the hook for that may not have run yet.
statement("if (", to_expression(builtin_invocation_id_id), " < ", "spvIndirectParams[0])");
statement(" ", input_wg_var_name, "[", to_expression(builtin_invocation_id_id), "] = ", ib_var_ref,
";");
statement("threadgroup_barrier(mem_flags::mem_threadgroup);");
statement("if (", to_expression(builtin_invocation_id_id), " >= ", get_entry_point().output_vertices,
")");
statement(" return;");
});
}
break;
case StorageClassOutput:
{
ib_var_ref = patch ? patch_stage_out_var_name : stage_out_var_name;
// Add the output interface struct as a local variable to the entry function.
// If the entry point should return the output struct, set the entry function
// to return the output interface struct, otherwise to return nothing.
// Indicate the output var requires early initialization.
bool ep_should_return_output = !get_is_rasterization_disabled();
uint32_t rtn_id = ep_should_return_output ? ib_var_id : 0;
if (!capture_output_to_buffer)
{
entry_func.add_local_variable(ib_var_id);
for (auto &blk_id : entry_func.blocks)
{
auto &blk = get<SPIRBlock>(blk_id);
if (blk.terminator == SPIRBlock::Return)
blk.return_value = rtn_id;
}
vars_needing_early_declaration.push_back(ib_var_id);
}
else
{
switch (get_execution_model())
{
case ExecutionModelVertex:
case ExecutionModelTessellationEvaluation:
// Instead of declaring a struct variable to hold the output and then
// copying that to the output buffer, we'll declare the output variable
// as a reference to the final output element in the buffer. Then we can
// avoid the extra copy.
entry_func.fixup_hooks_in.push_back([=]() {
if (stage_out_var_id)
{
// The first member of the indirect buffer is always the number of vertices
// to draw.
statement("device ", to_name(ir.default_entry_point), "_", ib_var_ref, "& ", ib_var_ref, " = ",
output_buffer_var_name, "[(", to_expression(builtin_instance_idx_id), " - ",
to_expression(builtin_base_instance_id), ") * spvIndirectParams[0] + ",
to_expression(builtin_vertex_idx_id), " - ", to_expression(builtin_base_vertex_id),
"];");
}
});
break;
case ExecutionModelTessellationControl:
if (patch)
entry_func.fixup_hooks_in.push_back([=]() {
statement("device ", to_name(ir.default_entry_point), "_", ib_var_ref, "& ", ib_var_ref, " = ",
patch_output_buffer_var_name, "[", to_expression(builtin_primitive_id_id), "];");
});
else
entry_func.fixup_hooks_in.push_back([=]() {
statement("device ", to_name(ir.default_entry_point), "_", ib_var_ref, "* gl_out = &",
output_buffer_var_name, "[", to_expression(builtin_primitive_id_id), " * ",
get_entry_point().output_vertices, "];");
});
break;
default:
break;
}
}
break;
}
default:
break;
}
set_name(ib_type_id, to_name(ir.default_entry_point) + "_" + ib_var_ref);
set_name(ib_var_id, ib_var_ref);
for (auto *p_var : vars)
{
bool strip_array =
(get_execution_model() == ExecutionModelTessellationControl ||
(get_execution_model() == ExecutionModelTessellationEvaluation && storage == StorageClassInput)) &&
!patch;
add_variable_to_interface_block(storage, ib_var_ref, ib_type, *p_var, strip_array);
}
// Sort the members of the structure by their locations.
MemberSorter member_sorter(ib_type, ir.meta[ib_type_id], MemberSorter::Location);
member_sorter.sort();
// The member indices were saved to the original variables, but after the members
// were sorted, those indices are now likely incorrect. Fix those up now.
if (!patch)
fix_up_interface_member_indices(storage, ib_type_id);
// For patch inputs, add one more member, holding the array of control point data.
if (get_execution_model() == ExecutionModelTessellationEvaluation && storage == StorageClassInput && patch &&
stage_in_var_id)
{
uint32_t pcp_type_id = ir.increase_bound_by(1);
auto &pcp_type = set<SPIRType>(pcp_type_id, ib_type);
pcp_type.basetype = SPIRType::ControlPointArray;
pcp_type.parent_type = pcp_type.type_alias = get_stage_in_struct_type().self;
pcp_type.storage = storage;
ir.meta[pcp_type_id] = ir.meta[ib_type.self];
uint32_t mbr_idx = uint32_t(ib_type.member_types.size());
ib_type.member_types.push_back(pcp_type_id);
set_member_name(ib_type.self, mbr_idx, "gl_in");
}
return ib_var_id;
}
uint32_t CompilerMSL::add_interface_block_pointer(uint32_t ib_var_id, StorageClass storage)
{
if (!ib_var_id)
return 0;
uint32_t ib_ptr_var_id;
uint32_t next_id = ir.increase_bound_by(3);
auto &ib_type = expression_type(ib_var_id);
if (get_execution_model() == ExecutionModelTessellationControl)
{
// Tessellation control per-vertex I/O is presented as an array, so we must
// do the same with our struct here.
uint32_t ib_ptr_type_id = next_id++;
auto &ib_ptr_type = set<SPIRType>(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.storage = storage == StorageClassInput ? StorageClassWorkgroup : StorageClassStorageBuffer;
ir.meta[ib_ptr_type_id] = ir.meta[ib_type.self];
// To ensure that get_variable_data_type() doesn't strip off the pointer,
// which we need, use another pointer.
uint32_t ib_ptr_ptr_type_id = next_id++;
auto &ib_ptr_ptr_type = set<SPIRType>(ib_ptr_ptr_type_id, ib_ptr_type);
ib_ptr_ptr_type.parent_type = ib_ptr_type_id;
ib_ptr_ptr_type.type_alias = ib_type.self;
ib_ptr_ptr_type.storage = StorageClassFunction;
ir.meta[ib_ptr_ptr_type_id] = ir.meta[ib_type.self];
ib_ptr_var_id = next_id;
set<SPIRVariable>(ib_ptr_var_id, ib_ptr_ptr_type_id, StorageClassFunction, 0);
set_name(ib_ptr_var_id, storage == StorageClassInput ? input_wg_var_name : "gl_out");
}
else
{
// Tessellation evaluation per-vertex inputs are also presented as arrays.
// But, in Metal, this array uses a very special type, 'patch_control_point<T>',
// which is a container that can be used to access the control point data.
// To represent this, a special 'ControlPointArray' type has been added to the
// SPIRV-Cross type system. It should only be generated by and seen in the MSL
// backend (i.e. this one).
uint32_t pcp_type_id = next_id++;
auto &pcp_type = set<SPIRType>(pcp_type_id, ib_type);
pcp_type.basetype = SPIRType::ControlPointArray;
pcp_type.parent_type = pcp_type.type_alias = ib_type.self;
pcp_type.storage = storage;
ir.meta[pcp_type_id] = ir.meta[ib_type.self];
ib_ptr_var_id = next_id;
set<SPIRVariable>(ib_ptr_var_id, pcp_type_id, storage, 0);
set_name(ib_ptr_var_id, "gl_in");
ir.meta[ib_ptr_var_id].decoration.qualified_alias = join(patch_stage_in_var_name, ".gl_in");
}
return ib_ptr_var_id;
}
// Ensure that the type is compatible with the builtin.
// If it is, simply return the given type ID.
// Otherwise, create a new type, and return it's ID.
uint32_t CompilerMSL::ensure_correct_builtin_type(uint32_t type_id, BuiltIn builtin)
{
auto &type = get<SPIRType>(type_id);
if ((builtin == BuiltInSampleMask && is_array(type)) ||
((builtin == BuiltInLayer || builtin == BuiltInViewportIndex || builtin == BuiltInFragStencilRefEXT) &&
type.basetype != SPIRType::UInt))
{
uint32_t next_id = ir.increase_bound_by(type.pointer ? 2 : 1);
uint32_t base_type_id = next_id++;
auto &base_type = set<SPIRType>(base_type_id);
base_type.basetype = SPIRType::UInt;
base_type.width = 32;
if (!type.pointer)
return base_type_id;
uint32_t ptr_type_id = next_id++;
auto &ptr_type = set<SPIRType>(ptr_type_id);
ptr_type = base_type;
ptr_type.pointer = true;
ptr_type.storage = type.storage;
ptr_type.parent_type = base_type_id;
return ptr_type_id;
}
return type_id;
}
// Ensure that the type is compatible with the vertex attribute.
// If it is, simply return the given type ID.
// Otherwise, create a new type, and return its ID.
uint32_t CompilerMSL::ensure_correct_attribute_type(uint32_t type_id, uint32_t location)
{
auto &type = get<SPIRType>(type_id);
auto p_va = vtx_attrs_by_location.find(location);
if (p_va == end(vtx_attrs_by_location))
return type_id;
switch (p_va->second.format)
{
case MSL_VERTEX_FORMAT_UINT8:
{
switch (type.basetype)
{
case SPIRType::UByte:
case SPIRType::UShort:
case SPIRType::UInt:
return type_id;
case SPIRType::Short:
case SPIRType::Int:
break;
default:
SPIRV_CROSS_THROW("Vertex attribute type mismatch between host and shader");
}
uint32_t next_id = ir.increase_bound_by(type.pointer ? 2 : 1);
uint32_t base_type_id = next_id++;
auto &base_type = set<SPIRType>(base_type_id);
base_type = type;
base_type.basetype = type.basetype == SPIRType::Short ? SPIRType::UShort : SPIRType::UInt;
base_type.pointer = false;
if (!type.pointer)
return base_type_id;
uint32_t ptr_type_id = next_id++;
auto &ptr_type = set<SPIRType>(ptr_type_id);
ptr_type = base_type;
ptr_type.pointer = true;
ptr_type.storage = type.storage;
ptr_type.parent_type = base_type_id;
return ptr_type_id;
}
case MSL_VERTEX_FORMAT_UINT16:
{
switch (type.basetype)
{
case SPIRType::UShort:
case SPIRType::UInt:
return type_id;
case SPIRType::Int:
break;
default:
SPIRV_CROSS_THROW("Vertex attribute type mismatch between host and shader");
}
uint32_t next_id = ir.increase_bound_by(type.pointer ? 2 : 1);
uint32_t base_type_id = next_id++;
auto &base_type = set<SPIRType>(base_type_id);
base_type = type;
base_type.basetype = SPIRType::UInt;
base_type.pointer = false;
if (!type.pointer)
return base_type_id;
uint32_t ptr_type_id = next_id++;
auto &ptr_type = set<SPIRType>(ptr_type_id);
ptr_type = base_type;
ptr_type.pointer = true;
ptr_type.storage = type.storage;
ptr_type.parent_type = base_type_id;
return ptr_type_id;
}
default:
case MSL_VERTEX_FORMAT_OTHER:
break;
}
return type_id;
}
void CompilerMSL::mark_struct_members_packed(const SPIRType &type)
{
set_extended_decoration(type.self, SPIRVCrossDecorationPhysicalTypePacked);
// Problem case! Struct needs to be placed at an awkward alignment.
// Mark every member of the child struct as packed.
uint32_t mbr_cnt = uint32_t(type.member_types.size());
for (uint32_t i = 0; i < mbr_cnt; i++)
{
auto &mbr_type = get<SPIRType>(type.member_types[i]);
if (mbr_type.basetype == SPIRType::Struct)
{
// Recursively mark structs as packed.
auto *struct_type = &mbr_type;
while (!struct_type->array.empty())
struct_type = &get<SPIRType>(struct_type->parent_type);
mark_struct_members_packed(*struct_type);
}
else if (!is_scalar(mbr_type))
set_extended_member_decoration(type.self, i, SPIRVCrossDecorationPhysicalTypePacked);
}
}
void CompilerMSL::mark_scalar_layout_structs(const SPIRType &type)
{
uint32_t mbr_cnt = uint32_t(type.member_types.size());
for (uint32_t i = 0; i < mbr_cnt; i++)
{
auto &mbr_type = get<SPIRType>(type.member_types[i]);
if (mbr_type.basetype == SPIRType::Struct)
{
auto *struct_type = &mbr_type;
while (!struct_type->array.empty())
struct_type = &get<SPIRType>(struct_type->parent_type);
if (has_extended_decoration(struct_type->self, SPIRVCrossDecorationPhysicalTypePacked))
continue;
uint32_t msl_alignment = get_declared_struct_member_alignment_msl(type, i);
uint32_t msl_size = get_declared_struct_member_size_msl(type, i);
uint32_t spirv_offset = type_struct_member_offset(type, i);
uint32_t spirv_offset_next;
if (i + 1 < mbr_cnt)
spirv_offset_next = type_struct_member_offset(type, i + 1);
else
spirv_offset_next = spirv_offset + msl_size;
// Both are complicated cases. In scalar layout, a struct of float3 might just consume 12 bytes,
// and the next member will be placed at offset 12.
bool struct_is_misaligned = (spirv_offset % msl_alignment) != 0;
bool struct_is_too_large = spirv_offset + msl_size > spirv_offset_next;
uint32_t array_stride = 0;
bool struct_needs_explicit_padding = false;
// Verify that if a struct is used as an array that ArrayStride matches the effective size of the struct.
if (!mbr_type.array.empty())
{
array_stride = type_struct_member_array_stride(type, i);
uint32_t dimensions = uint32_t(mbr_type.array.size() - 1);
for (uint32_t dim = 0; dim < dimensions; dim++)
{
uint32_t array_size = to_array_size_literal(mbr_type, dim);
array_stride /= max(array_size, 1u);
}
// Set expected struct size based on ArrayStride.
struct_needs_explicit_padding = true;
// If struct size is larger than array stride, we might be able to fit, if we tightly pack.
if (get_declared_struct_size_msl(*struct_type) > array_stride)
struct_is_too_large = true;
}
if (struct_is_misaligned || struct_is_too_large)
mark_struct_members_packed(*struct_type);
mark_scalar_layout_structs(*struct_type);
if (struct_needs_explicit_padding)
{
msl_size = get_declared_struct_size_msl(*struct_type, true, true);
if (array_stride < msl_size)
{
SPIRV_CROSS_THROW("Cannot express an array stride smaller than size of struct type.");
}
else
{
if (has_extended_decoration(struct_type->self, SPIRVCrossDecorationPaddingTarget))
{
if (array_stride !=
get_extended_decoration(struct_type->self, SPIRVCrossDecorationPaddingTarget))
SPIRV_CROSS_THROW(
"A struct is used with different array strides. Cannot express this in MSL.");
}
else
set_extended_decoration(struct_type->self, SPIRVCrossDecorationPaddingTarget, array_stride);
}
}
}
}
}
// Sort the members of the struct type by offset, and pack and then pad members where needed
// to align MSL members with SPIR-V offsets. The struct members are iterated twice. Packing
// occurs first, followed by padding, because packing a member reduces both its size and its
// natural alignment, possibly requiring a padding member to be added ahead of it.
void CompilerMSL::align_struct(SPIRType &ib_type, unordered_set<uint32_t> &aligned_structs)
{
// We align structs recursively, so stop any redundant work.
uint32_t &ib_type_id = ib_type.self;
if (aligned_structs.count(ib_type_id))
return;
aligned_structs.insert(ib_type_id);
// Sort the members of the interface structure by their offset.
// They should already be sorted per SPIR-V spec anyway.
MemberSorter member_sorter(ib_type, ir.meta[ib_type_id], MemberSorter::Offset);
member_sorter.sort();
auto mbr_cnt = uint32_t(ib_type.member_types.size());
for (uint32_t mbr_idx = 0; mbr_idx < mbr_cnt; mbr_idx++)
{
// Pack any dependent struct types before we pack a parent struct.
auto &mbr_type = get<SPIRType>(ib_type.member_types[mbr_idx]);
if (mbr_type.basetype == SPIRType::Struct)
align_struct(mbr_type, aligned_structs);
}
// Test the alignment of each member, and if a member should be closer to the previous
// member than the default spacing expects, it is likely that the previous member is in
// a packed format. If so, and the previous member is packable, pack it.
// For example ... this applies to any 3-element vector that is followed by a scalar.
uint32_t msl_offset = 0;
for (uint32_t mbr_idx = 0; mbr_idx < mbr_cnt; mbr_idx++)
{
// This checks the member in isolation, if the member needs some kind of type remapping to conform to SPIR-V
// offsets, array strides and matrix strides.
ensure_member_packing_rules_msl(ib_type, mbr_idx);
// Align current offset to the current member's default alignment. If the member was packed, it will observe
// the updated alignment here.
uint32_t msl_align_mask = get_declared_struct_member_alignment_msl(ib_type, mbr_idx) - 1;
uint32_t aligned_msl_offset = (msl_offset + msl_align_mask) & ~msl_align_mask;
// Fetch the member offset as declared in the SPIRV.
uint32_t spirv_mbr_offset = get_member_decoration(ib_type_id, mbr_idx, DecorationOffset);
if (spirv_mbr_offset > aligned_msl_offset)
{
// Since MSL and SPIR-V have slightly different struct member alignment and
// size rules, we'll pad to standard C-packing rules with a char[] array. If the member is farther
// away than C-packing, expects, add an inert padding member before the the member.
uint32_t padding_bytes = spirv_mbr_offset - aligned_msl_offset;
set_extended_member_decoration(ib_type_id, mbr_idx, SPIRVCrossDecorationPaddingTarget, padding_bytes);
// Re-align as a sanity check that aligning post-padding matches up.
msl_offset += padding_bytes;
aligned_msl_offset = (msl_offset + msl_align_mask) & ~msl_align_mask;
}
else if (spirv_mbr_offset < aligned_msl_offset)
{
// This should not happen, but deal with unexpected scenarios.
// It *might* happen if a sub-struct has a larger alignment requirement in MSL than SPIR-V.
SPIRV_CROSS_THROW("Cannot represent buffer block correctly in MSL.");
}
assert(aligned_msl_offset == spirv_mbr_offset);
// Increment the current offset to be positioned immediately after the current member.
// Don't do this for the last member since it can be unsized, and it is not relevant for padding purposes here.
if (mbr_idx + 1 < mbr_cnt)
msl_offset = aligned_msl_offset + get_declared_struct_member_size_msl(ib_type, mbr_idx);
}
}
bool CompilerMSL::validate_member_packing_rules_msl(const SPIRType &type, uint32_t index) const
{
auto &mbr_type = get<SPIRType>(type.member_types[index]);
uint32_t spirv_offset = get_member_decoration(type.self, index, DecorationOffset);
if (index + 1 < type.member_types.size())
{
// First, we will check offsets. If SPIR-V offset + MSL size > SPIR-V offset of next member,
// we *must* perform some kind of remapping, no way getting around it.
// We can always pad after this member if necessary, so that case is fine.
uint32_t spirv_offset_next = get_member_decoration(type.self, index + 1, DecorationOffset);
assert(spirv_offset_next >= spirv_offset);
uint32_t maximum_size = spirv_offset_next - spirv_offset;
uint32_t msl_mbr_size = get_declared_struct_member_size_msl(type, index);
if (msl_mbr_size > maximum_size)
return false;
}
if (!mbr_type.array.empty())
{
// If we have an array type, array stride must match exactly with SPIR-V.
uint32_t spirv_array_stride = type_struct_member_array_stride(type, index);
uint32_t msl_array_stride = get_declared_struct_member_array_stride_msl(type, index);
if (spirv_array_stride != msl_array_stride)
return false;
}
if (is_matrix(mbr_type))
{
// Need to check MatrixStride as well.
uint32_t spirv_matrix_stride = type_struct_member_matrix_stride(type, index);
uint32_t msl_matrix_stride = get_declared_struct_member_matrix_stride_msl(type, index);
if (spirv_matrix_stride != msl_matrix_stride)
return false;
}
// Now, we check alignment.
uint32_t msl_alignment = get_declared_struct_member_alignment_msl(type, index);
if ((spirv_offset % msl_alignment) != 0)
return false;
// We're in the clear.
return true;
}
// Here we need to verify that the member type we declare conforms to Offset, ArrayStride or MatrixStride restrictions.
// If there is a mismatch, we need to emit remapped types, either normal types, or "packed_X" types.
// In odd cases we need to emit packed and remapped types, for e.g. weird matrices or arrays with weird array strides.
void CompilerMSL::ensure_member_packing_rules_msl(SPIRType &ib_type, uint32_t index)
{
if (validate_member_packing_rules_msl(ib_type, index))
return;
// We failed validation.
// This case will be nightmare-ish to deal with. This could possibly happen if struct alignment does not quite
// match up with what we want. Scalar block layout comes to mind here where we might have to work around the rule
// that struct alignment == max alignment of all members and struct size depends on this alignment.
auto &mbr_type = get<SPIRType>(ib_type.member_types[index]);
if (mbr_type.basetype == SPIRType::Struct)
SPIRV_CROSS_THROW("Cannot perform any repacking for structs when it is used as a member of another struct.");
// Perform remapping here.
set_extended_member_decoration(ib_type.self, index, SPIRVCrossDecorationPhysicalTypePacked);
// Try validating again, now with packed.
if (validate_member_packing_rules_msl(ib_type, index))
return;
// We're in deep trouble, and we need to create a new PhysicalType which matches up with what we expect.
// A lot of work goes here ...
// We will need remapping on Load and Store to translate the types between Logical and Physical.
// First, we check if we have small vector std140 array.
// We detect this if we have an array of vectors, and array stride is greater than number of elements.
if (!mbr_type.array.empty() && !is_matrix(mbr_type))
{
uint32_t array_stride = type_struct_member_array_stride(ib_type, index);
// Hack off array-of-arrays until we find the array stride per element we must have to make it work.
uint32_t dimensions = uint32_t(mbr_type.array.size() - 1);
for (uint32_t dim = 0; dim < dimensions; dim++)
array_stride /= max(to_array_size_literal(mbr_type, dim), 1u);
uint32_t elems_per_stride = array_stride / (mbr_type.width / 8);
if (elems_per_stride == 3)
SPIRV_CROSS_THROW("Cannot use ArrayStride of 3 elements in remapping scenarios.");
else if (elems_per_stride > 4)
SPIRV_CROSS_THROW("Cannot represent vectors with more than 4 elements in MSL.");
auto physical_type = mbr_type;
physical_type.vecsize = elems_per_stride;
physical_type.parent_type = 0;
uint32_t type_id = ir.increase_bound_by(1);
set<SPIRType>(type_id, physical_type);
set_extended_member_decoration(ib_type.self, index, SPIRVCrossDecorationPhysicalTypeID, type_id);
set_decoration(type_id, DecorationArrayStride, array_stride);
// Remove packed_ for vectors of size 1, 2 and 4.
if (has_extended_decoration(ib_type.self, SPIRVCrossDecorationPhysicalTypePacked))
SPIRV_CROSS_THROW("Unable to remove packed decoration as entire struct must be fully packed. Do not mix "
"scalar and std140 layout rules.");
else
unset_extended_member_decoration(ib_type.self, index, SPIRVCrossDecorationPhysicalTypePacked);
}
else if (is_matrix(mbr_type))
{
// MatrixStride might be std140-esque.
uint32_t matrix_stride = type_struct_member_matrix_stride(ib_type, index);
uint32_t elems_per_stride = matrix_stride / (mbr_type.width / 8);
if (elems_per_stride == 3)
SPIRV_CROSS_THROW("Cannot use ArrayStride of 3 elements in remapping scenarios.");
else if (elems_per_stride > 4)
SPIRV_CROSS_THROW("Cannot represent vectors with more than 4 elements in MSL.");
bool row_major = has_member_decoration(ib_type.self, index, DecorationRowMajor);
auto physical_type = mbr_type;
physical_type.parent_type = 0;
if (row_major)
physical_type.columns = elems_per_stride;
else
physical_type.vecsize = elems_per_stride;
uint32_t type_id = ir.increase_bound_by(1);
set<SPIRType>(type_id, physical_type);
set_extended_member_decoration(ib_type.self, index, SPIRVCrossDecorationPhysicalTypeID, type_id);
// Remove packed_ for vectors of size 1, 2 and 4.
if (has_extended_decoration(ib_type.self, SPIRVCrossDecorationPhysicalTypePacked))
SPIRV_CROSS_THROW("Unable to remove packed decoration as entire struct must be fully packed. Do not mix "
"scalar and std140 layout rules.");
else
unset_extended_member_decoration(ib_type.self, index, SPIRVCrossDecorationPhysicalTypePacked);
}
// This better validate now, or we must fail gracefully.
if (!validate_member_packing_rules_msl(ib_type, index))
SPIRV_CROSS_THROW("Found a buffer packing case which we cannot represent in MSL.");
}
void CompilerMSL::emit_store_statement(uint32_t lhs_expression, uint32_t rhs_expression)
{
auto &type = expression_type(rhs_expression);
bool lhs_remapped_type = has_extended_decoration(lhs_expression, SPIRVCrossDecorationPhysicalTypeID);
bool lhs_packed_type = has_extended_decoration(lhs_expression, SPIRVCrossDecorationPhysicalTypePacked);
auto *lhs_e = maybe_get<SPIRExpression>(lhs_expression);
auto *rhs_e = maybe_get<SPIRExpression>(rhs_expression);
bool transpose = lhs_e && lhs_e->need_transpose;
// No physical type remapping, and no packed type, so can just emit a store directly.
if (!lhs_remapped_type && !lhs_packed_type)
{
// We might not be dealing with remapped physical types or packed types,
// but we might be doing a clean store to a row-major matrix.
// In this case, we just flip transpose states, and emit the store, a transpose must be in the RHS expression, if any.
if (is_matrix(type) && lhs_e && lhs_e->need_transpose)
{
if (!rhs_e)
SPIRV_CROSS_THROW("Need to transpose right-side expression of a store to row-major matrix, but it is "
"not a SPIRExpression.");
lhs_e->need_transpose = false;
if (rhs_e && rhs_e->need_transpose)
{
// Direct copy, but might need to unpack RHS.
// Skip the transpose, as we will transpose when writing to LHS and transpose(transpose(T)) == T.
rhs_e->need_transpose = false;
statement(to_expression(lhs_expression), " = ", to_unpacked_row_major_matrix_expression(rhs_expression),
";");
rhs_e->need_transpose = true;
}
else
statement(to_expression(lhs_expression), " = transpose(", to_unpacked_expression(rhs_expression), ");");
lhs_e->need_transpose