| /* |
| * 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 |