blob: f2b74d5a1fc98ec4335f57fcba9409bc8c62044d [file] [log] [blame] [edit]
/*
* Copyright 2016-2021 The Brenwill Workshop Ltd.
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
* 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.
*/
/*
* At your option, you may choose to accept this material under either:
* 1. The Apache License, Version 2.0, found at <http://www.apache.org/licenses/LICENSE-2.0>, or
* 2. The MIT License, found at <http://opensource.org/licenses/MIT>.
*/
#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;
static const char *force_inline = "static inline __attribute__((always_inline))";
CompilerMSL::CompilerMSL(std::vector<uint32_t> spirv_)
: CompilerGLSL(std::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_shader_input(const MSLShaderInterfaceVariable &si)
{
inputs_by_location[{si.location, si.component}] = si;
if (si.builtin != BuiltInMax && !inputs_by_builtin.count(si.builtin))
inputs_by_builtin[si.builtin] = si;
}
void CompilerMSL::add_msl_shader_output(const MSLShaderInterfaceVariable &so)
{
outputs_by_location[{so.location, so.component}] = so;
if (so.builtin != BuiltInMax && !outputs_by_builtin.count(so.builtin))
outputs_by_builtin[so.builtin] = so;
}
void CompilerMSL::add_msl_resource_binding(const MSLResourceBinding &binding)
{
StageSetBinding tuple = { binding.stage, binding.desc_set, binding.binding };
resource_bindings[tuple] = { binding, false };
// If we might need to pad argument buffer members to positionally align
// arg buffer indexes, also maintain a lookup by argument buffer index.
if (msl_options.pad_argument_buffer_resources)
{
StageSetBinding arg_idx_tuple = { binding.stage, binding.desc_set, k_unknown_component };
#define ADD_ARG_IDX_TO_BINDING_NUM_LOOKUP(rez) \
arg_idx_tuple.binding = binding.msl_##rez; \
resource_arg_buff_idx_to_binding_number[arg_idx_tuple] = binding.binding
switch (binding.basetype)
{
case SPIRType::Void:
case SPIRType::Boolean:
case SPIRType::SByte:
case SPIRType::UByte:
case SPIRType::Short:
case SPIRType::UShort:
case SPIRType::Int:
case SPIRType::UInt:
case SPIRType::Int64:
case SPIRType::UInt64:
case SPIRType::AtomicCounter:
case SPIRType::Half:
case SPIRType::Float:
case SPIRType::Double:
ADD_ARG_IDX_TO_BINDING_NUM_LOOKUP(buffer);
break;
case SPIRType::Image:
ADD_ARG_IDX_TO_BINDING_NUM_LOOKUP(texture);
break;
case SPIRType::Sampler:
ADD_ARG_IDX_TO_BINDING_NUM_LOOKUP(sampler);
break;
case SPIRType::SampledImage:
ADD_ARG_IDX_TO_BINDING_NUM_LOOKUP(texture);
ADD_ARG_IDX_TO_BINDING_NUM_LOOKUP(sampler);
break;
default:
SPIRV_CROSS_THROW("Unexpected argument buffer resource base type. When padding argument buffer elements, "
"all descriptor set resources must be supplied with a base type by the app.");
}
#undef ADD_ARG_IDX_TO_BINDING_NUM_LOOKUP
}
}
void CompilerMSL::add_dynamic_buffer(uint32_t desc_set, uint32_t binding, uint32_t index)
{
SetBindingPair pair = { desc_set, binding };
buffers_requiring_dynamic_offset[pair] = { index, 0 };
}
void CompilerMSL::add_inline_uniform_block(uint32_t desc_set, uint32_t binding)
{
SetBindingPair pair = { desc_set, binding };
inline_uniform_blocks.insert(pair);
}
void CompilerMSL::add_discrete_descriptor_set(uint32_t desc_set)
{
if (desc_set < kMaxArgumentBuffers)
argument_buffer_discrete_mask |= 1u << desc_set;
}
void CompilerMSL::set_argument_buffer_device_address_space(uint32_t desc_set, bool device_storage)
{
if (desc_set < kMaxArgumentBuffers)
{
if (device_storage)
argument_buffer_device_storage_mask |= 1u << desc_set;
else
argument_buffer_device_storage_mask &= ~(1u << desc_set);
}
}
bool CompilerMSL::is_msl_shader_input_used(uint32_t location)
{
// Don't report internal location allocations to app.
return location_inputs_in_use.count(location) != 0 &&
location_inputs_in_use_fallback.count(location) == 0;
}
bool CompilerMSL::is_msl_shader_output_used(uint32_t location)
{
// Don't report internal location allocations to app.
return location_outputs_in_use.count(location) != 0 &&
location_outputs_in_use_fallback.count(location) == 0;
}
uint32_t CompilerMSL::get_automatic_builtin_input_location(spv::BuiltIn builtin) const
{
auto itr = builtin_to_automatic_input_location.find(builtin);
if (itr == builtin_to_automatic_input_location.end())
return k_unknown_location;
else
return itr->second;
}
uint32_t CompilerMSL::get_automatic_builtin_output_location(spv::BuiltIn builtin) const
{
auto itr = builtin_to_automatic_output_location.find(builtin);
if (itr == builtin_to_automatic_output_location.end())
return k_unknown_location;
else
return itr->second;
}
bool CompilerMSL::is_msl_resource_binding_used(ExecutionModel model, uint32_t desc_set, uint32_t binding) const
{
StageSetBinding tuple = { model, desc_set, binding };
auto itr = resource_bindings.find(tuple);
return itr != end(resource_bindings) && itr->second.second;
}
// Returns the size of the array of resources used by the variable with the specified id.
// The returned value is retrieved from the resource binding added using add_msl_resource_binding().
uint32_t CompilerMSL::get_resource_array_size(uint32_t id) const
{
StageSetBinding tuple = { get_entry_point().model, get_decoration(id, DecorationDescriptorSet),
get_decoration(id, DecorationBinding) };
auto itr = resource_bindings.find(tuple);
return itr != end(resource_bindings) ? itr->second.first.count : 0;
}
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);
}
uint32_t CompilerMSL::get_automatic_msl_resource_binding_tertiary(uint32_t id) const
{
return get_extended_decoration(id, SPIRVCrossDecorationResourceIndexTertiary);
}
uint32_t CompilerMSL::get_automatic_msl_resource_binding_quaternary(uint32_t id) const
{
return get_extended_decoration(id, SPIRVCrossDecorationResourceIndexQuaternary);
}
void CompilerMSL::set_fragment_output_components(uint32_t location, uint32_t components)
{
fragment_output_components[location] = components;
}
bool CompilerMSL::builtin_translates_to_nonarray(spv::BuiltIn builtin) const
{
return (builtin == BuiltInSampleMask);
}
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 &&
!msl_options.vertex_for_tessellation;
bool need_tesc_params = is_tesc_shader();
bool need_tese_params = is_tese_shader() && msl_options.raw_buffer_tese_input;
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_layered_rendering &&
(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));
bool need_grid_params = get_execution_model() == ExecutionModelVertex && msl_options.vertex_for_tessellation;
bool need_vertex_base_params =
need_grid_params &&
(active_input_builtins.get(BuiltInVertexId) || active_input_builtins.get(BuiltInVertexIndex) ||
active_input_builtins.get(BuiltInBaseVertex) || active_input_builtins.get(BuiltInInstanceId) ||
active_input_builtins.get(BuiltInInstanceIndex) || active_input_builtins.get(BuiltInBaseInstance));
bool need_local_invocation_index = msl_options.emulate_subgroups && active_input_builtins.get(BuiltInSubgroupId);
bool need_workgroup_size = msl_options.emulate_subgroups && active_input_builtins.get(BuiltInNumSubgroups);
if (need_subpass_input || need_sample_pos || need_subgroup_mask || need_vertex_params || need_tesc_params ||
need_tese_params || need_multiview || need_dispatch_base || need_vertex_base_params || need_grid_params ||
needs_sample_id || needs_subgroup_invocation_id || needs_subgroup_size || needs_helper_invocation ||
has_additional_fixed_sample_mask() || need_local_invocation_index || need_workgroup_size)
{
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;
bool has_layer = false;
bool has_helper_invocation = false;
bool has_local_invocation_index = false;
bool has_workgroup_size = false;
uint32_t workgroup_id_type = 0;
ir.for_each_typed_id<SPIRVariable>([&](uint32_t, SPIRVariable &var) {
if (var.storage != StorageClassInput && var.storage != StorageClassOutput)
return;
if (!interface_variable_exists_in_entry_point(var.self))
return;
if (!has_decoration(var.self, DecorationBuiltIn))
return;
BuiltIn builtin = ir.meta[var.self].decoration.builtin_type;
if (var.storage == StorageClassOutput)
{
if (has_additional_fixed_sample_mask() && builtin == BuiltInSampleMask)
{
builtin_sample_mask_id = var.self;
mark_implicit_builtin(StorageClassOutput, BuiltInSampleMask, var.self);
does_shader_write_sample_mask = true;
}
}
if (var.storage != StorageClassInput)
return;
// Use Metal's native frame-buffer fetch API for subpass inputs.
if (need_subpass_input && (!msl_options.use_framebuffer_fetch_subpasses))
{
switch (builtin)
{
case BuiltInFragCoord:
mark_implicit_builtin(StorageClassInput, BuiltInFragCoord, var.self);
builtin_frag_coord_id = var.self;
has_frag_coord = true;
break;
case BuiltInLayer:
if (!msl_options.arrayed_subpass_input || msl_options.multiview)
break;
mark_implicit_builtin(StorageClassInput, BuiltInLayer, var.self);
builtin_layer_id = var.self;
has_layer = true;
break;
case BuiltInViewIndex:
if (!msl_options.multiview)
break;
mark_implicit_builtin(StorageClassInput, BuiltInViewIndex, var.self);
builtin_view_idx_id = var.self;
has_view_idx = true;
break;
default:
break;
}
}
if ((need_sample_pos || needs_sample_id) && builtin == BuiltInSampleId)
{
builtin_sample_id_id = var.self;
mark_implicit_builtin(StorageClassInput, BuiltInSampleId, var.self);
has_sample_id = true;
}
if (need_vertex_params)
{
switch (builtin)
{
case BuiltInVertexIndex:
builtin_vertex_idx_id = var.self;
mark_implicit_builtin(StorageClassInput, BuiltInVertexIndex, var.self);
has_vertex_idx = true;
break;
case BuiltInBaseVertex:
builtin_base_vertex_id = var.self;
mark_implicit_builtin(StorageClassInput, BuiltInBaseVertex, var.self);
has_base_vertex = true;
break;
case BuiltInInstanceIndex:
builtin_instance_idx_id = var.self;
mark_implicit_builtin(StorageClassInput, BuiltInInstanceIndex, var.self);
has_instance_idx = true;
break;
case BuiltInBaseInstance:
builtin_base_instance_id = var.self;
mark_implicit_builtin(StorageClassInput, BuiltInBaseInstance, var.self);
has_base_instance = true;
break;
default:
break;
}
}
if (need_tesc_params && builtin == BuiltInInvocationId)
{
builtin_invocation_id_id = var.self;
mark_implicit_builtin(StorageClassInput, BuiltInInvocationId, var.self);
has_invocation_id = true;
}
if ((need_tesc_params || need_tese_params) && builtin == BuiltInPrimitiveId)
{
builtin_primitive_id_id = var.self;
mark_implicit_builtin(StorageClassInput, BuiltInPrimitiveId, var.self);
has_primitive_id = true;
}
if (need_tese_params && builtin == BuiltInTessLevelOuter)
{
tess_level_outer_var_id = var.self;
}
if (need_tese_params && builtin == BuiltInTessLevelInner)
{
tess_level_inner_var_id = var.self;
}
if ((need_subgroup_mask || needs_subgroup_invocation_id) && builtin == BuiltInSubgroupLocalInvocationId)
{
builtin_subgroup_invocation_id_id = var.self;
mark_implicit_builtin(StorageClassInput, BuiltInSubgroupLocalInvocationId, var.self);
has_subgroup_invocation_id = true;
}
if ((need_subgroup_ge_mask || needs_subgroup_size) && builtin == BuiltInSubgroupSize)
{
builtin_subgroup_size_id = var.self;
mark_implicit_builtin(StorageClassInput, BuiltInSubgroupSize, var.self);
has_subgroup_size = true;
}
if (need_multiview)
{
switch (builtin)
{
case BuiltInInstanceIndex:
// The view index here is derived from the instance index.
builtin_instance_idx_id = var.self;
mark_implicit_builtin(StorageClassInput, BuiltInInstanceIndex, var.self);
has_instance_idx = true;
break;
case BuiltInBaseInstance:
// If a non-zero base instance is used, we need to adjust for it when calculating the view index.
builtin_base_instance_id = var.self;
mark_implicit_builtin(StorageClassInput, BuiltInBaseInstance, var.self);
has_base_instance = true;
break;
case BuiltInViewIndex:
builtin_view_idx_id = var.self;
mark_implicit_builtin(StorageClassInput, BuiltInViewIndex, var.self);
has_view_idx = true;
break;
default:
break;
}
}
if (needs_helper_invocation && builtin == BuiltInHelperInvocation)
{
builtin_helper_invocation_id = var.self;
mark_implicit_builtin(StorageClassInput, BuiltInHelperInvocation, var.self);
has_helper_invocation = true;
}
if (need_local_invocation_index && builtin == BuiltInLocalInvocationIndex)
{
builtin_local_invocation_index_id = var.self;
mark_implicit_builtin(StorageClassInput, BuiltInLocalInvocationIndex, var.self);
has_local_invocation_index = true;
}
if (need_workgroup_size && builtin == BuiltInLocalInvocationId)
{
builtin_workgroup_size_id = var.self;
mark_implicit_builtin(StorageClassInput, BuiltInWorkgroupSize, var.self);
has_workgroup_size = 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;
});
// Use Metal's native frame-buffer fetch API for subpass inputs.
if ((!has_frag_coord || (msl_options.multiview && !has_view_idx) ||
(msl_options.arrayed_subpass_input && !msl_options.multiview && !has_layer)) &&
(!msl_options.use_framebuffer_fetch_subpasses) && need_subpass_input)
{
if (!has_frag_coord)
{
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.pointer_depth++;
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_layer && msl_options.arrayed_subpass_input && !msl_options.multiview)
{
uint32_t offset = ir.increase_bound_by(2);
uint32_t type_ptr_id = offset;
uint32_t var_id = offset + 1;
// Create gl_Layer.
SPIRType uint_type_ptr;
uint_type_ptr = get_uint_type();
uint_type_ptr.pointer = true;
uint_type_ptr.pointer_depth++;
uint_type_ptr.parent_type = get_uint_type_id();
uint_type_ptr.storage = StorageClassInput;
auto &ptr_type = set<SPIRType>(type_ptr_id, uint_type_ptr);
ptr_type.self = get_uint_type_id();
set<SPIRVariable>(var_id, type_ptr_id, StorageClassInput);
set_decoration(var_id, DecorationBuiltIn, BuiltInLayer);
builtin_layer_id = var_id;
mark_implicit_builtin(StorageClassInput, BuiltInLayer, var_id);
}
if (!has_view_idx && msl_options.multiview)
{
uint32_t offset = ir.increase_bound_by(2);
uint32_t type_ptr_id = offset;
uint32_t var_id = offset + 1;
// Create gl_ViewIndex.
SPIRType uint_type_ptr;
uint_type_ptr = get_uint_type();
uint_type_ptr.pointer = true;
uint_type_ptr.pointer_depth++;
uint_type_ptr.parent_type = get_uint_type_id();
uint_type_ptr.storage = StorageClassInput;
auto &ptr_type = set<SPIRType>(type_ptr_id, uint_type_ptr);
ptr_type.self = get_uint_type_id();
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 (!has_sample_id && (need_sample_pos || needs_sample_id))
{
uint32_t offset = ir.increase_bound_by(2);
uint32_t type_ptr_id = offset;
uint32_t var_id = offset + 1;
// Create gl_SampleID.
SPIRType uint_type_ptr;
uint_type_ptr = get_uint_type();
uint_type_ptr.pointer = true;
uint_type_ptr.pointer_depth++;
uint_type_ptr.parent_type = get_uint_type_id();
uint_type_ptr.storage = StorageClassInput;
auto &ptr_type = set<SPIRType>(type_ptr_id, uint_type_ptr);
ptr_type.self = get_uint_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_base_instance || !has_view_idx)))
{
uint32_t type_ptr_id = ir.increase_bound_by(1);
SPIRType uint_type_ptr;
uint_type_ptr = get_uint_type();
uint_type_ptr.pointer = true;
uint_type_ptr.pointer_depth++;
uint_type_ptr.parent_type = get_uint_type_id();
uint_type_ptr.storage = StorageClassInput;
auto &ptr_type = set<SPIRType>(type_ptr_id, uint_type_ptr);
ptr_type.self = get_uint_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 (!has_base_instance) // Needed by both multiview and tessellation
{
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 = get_uint_type();
uint_type_ptr_out.pointer = true;
uint_type_ptr_out.pointer_depth++;
uint_type_ptr_out.parent_type = get_uint_type_id();
uint_type_ptr_out.storage = StorageClassOutput;
auto &ptr_out_type = set<SPIRType>(type_ptr_out_id, uint_type_ptr_out);
ptr_out_type.self = get_uint_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 && (msl_options.multi_patch_workgroup || !has_invocation_id || !has_primitive_id)) ||
(need_tese_params && !has_primitive_id) || need_grid_params)
{
uint32_t type_ptr_id = ir.increase_bound_by(1);
SPIRType uint_type_ptr;
uint_type_ptr = get_uint_type();
uint_type_ptr.pointer = true;
uint_type_ptr.pointer_depth++;
uint_type_ptr.parent_type = get_uint_type_id();
uint_type_ptr.storage = StorageClassInput;
auto &ptr_type = set<SPIRType>(type_ptr_id, uint_type_ptr);
ptr_type.self = get_uint_type_id();
if ((need_tesc_params && msl_options.multi_patch_workgroup) || need_grid_params)
{
uint32_t var_id = ir.increase_bound_by(1);
// Create gl_GlobalInvocationID.
set<SPIRVariable>(var_id, type_ptr_id, StorageClassInput);
set_decoration(var_id, DecorationBuiltIn, BuiltInGlobalInvocationId);
builtin_invocation_id_id = var_id;
mark_implicit_builtin(StorageClassInput, BuiltInGlobalInvocationId, var_id);
}
else if (need_tesc_params && !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 ((need_tesc_params || need_tese_params) && !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 (need_grid_params)
{
uint32_t var_id = ir.increase_bound_by(1);
set<SPIRVariable>(var_id, build_extended_vector_type(get_uint_type_id(), 3), StorageClassInput);
set_extended_decoration(var_id, SPIRVCrossDecorationBuiltInStageInputSize);
get_entry_point().interface_variables.push_back(var_id);
set_name(var_id, "spvStageInputSize");
builtin_stage_input_size_id = var_id;
}
}
if (!has_subgroup_invocation_id && (need_subgroup_mask || needs_subgroup_invocation_id))
{
uint32_t offset = ir.increase_bound_by(2);
uint32_t type_ptr_id = offset;
uint32_t var_id = offset + 1;
// Create gl_SubgroupInvocationID.
SPIRType uint_type_ptr;
uint_type_ptr = get_uint_type();
uint_type_ptr.pointer = true;
uint_type_ptr.pointer_depth++;
uint_type_ptr.parent_type = get_uint_type_id();
uint_type_ptr.storage = StorageClassInput;
auto &ptr_type = set<SPIRType>(type_ptr_id, uint_type_ptr);
ptr_type.self = get_uint_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 || needs_subgroup_size))
{
uint32_t offset = ir.increase_bound_by(2);
uint32_t type_ptr_id = offset;
uint32_t var_id = offset + 1;
// Create gl_SubgroupSize.
SPIRType uint_type_ptr;
uint_type_ptr = get_uint_type();
uint_type_ptr.pointer = true;
uint_type_ptr.pointer_depth++;
uint_type_ptr.parent_type = get_uint_type_id();
uint_type_ptr.storage = StorageClassInput;
auto &ptr_type = set<SPIRType>(type_ptr_id, uint_type_ptr);
ptr_type.self = get_uint_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 || need_vertex_base_params)
{
if (workgroup_id_type == 0)
workgroup_id_type = build_extended_vector_type(get_uint_type_id(), 3);
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 (has_additional_fixed_sample_mask() && !does_shader_write_sample_mask)
{
uint32_t offset = ir.increase_bound_by(2);
uint32_t var_id = offset + 1;
// Create gl_SampleMask.
SPIRType uint_type_ptr_out;
uint_type_ptr_out = get_uint_type();
uint_type_ptr_out.pointer = true;
uint_type_ptr_out.pointer_depth++;
uint_type_ptr_out.parent_type = get_uint_type_id();
uint_type_ptr_out.storage = StorageClassOutput;
auto &ptr_out_type = set<SPIRType>(offset, uint_type_ptr_out);
ptr_out_type.self = get_uint_type_id();
set<SPIRVariable>(var_id, offset, StorageClassOutput);
set_decoration(var_id, DecorationBuiltIn, BuiltInSampleMask);
builtin_sample_mask_id = var_id;
mark_implicit_builtin(StorageClassOutput, BuiltInSampleMask, var_id);
}
if (!has_helper_invocation && needs_helper_invocation)
{
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_HelperInvocation.
SPIRType bool_type;
bool_type.basetype = SPIRType::Boolean;
bool_type.width = 8;
bool_type.vecsize = 1;
set<SPIRType>(type_id, bool_type);
SPIRType bool_type_ptr_in;
bool_type_ptr_in = bool_type;
bool_type_ptr_in.pointer = true;
bool_type_ptr_in.pointer_depth++;
bool_type_ptr_in.parent_type = type_id;
bool_type_ptr_in.storage = StorageClassInput;
auto &ptr_in_type = set<SPIRType>(type_ptr_id, bool_type_ptr_in);
ptr_in_type.self = type_id;
set<SPIRVariable>(var_id, type_ptr_id, StorageClassInput);
set_decoration(var_id, DecorationBuiltIn, BuiltInHelperInvocation);
builtin_helper_invocation_id = var_id;
mark_implicit_builtin(StorageClassInput, BuiltInHelperInvocation, var_id);
}
if (need_local_invocation_index && !has_local_invocation_index)
{
uint32_t offset = ir.increase_bound_by(2);
uint32_t type_ptr_id = offset;
uint32_t var_id = offset + 1;
// Create gl_LocalInvocationIndex.
SPIRType uint_type_ptr;
uint_type_ptr = get_uint_type();
uint_type_ptr.pointer = true;
uint_type_ptr.pointer_depth++;
uint_type_ptr.parent_type = get_uint_type_id();
uint_type_ptr.storage = StorageClassInput;
auto &ptr_type = set<SPIRType>(type_ptr_id, uint_type_ptr);
ptr_type.self = get_uint_type_id();
set<SPIRVariable>(var_id, type_ptr_id, StorageClassInput);
set_decoration(var_id, DecorationBuiltIn, BuiltInLocalInvocationIndex);
builtin_local_invocation_index_id = var_id;
mark_implicit_builtin(StorageClassInput, BuiltInLocalInvocationIndex, var_id);
}
if (need_workgroup_size && !has_workgroup_size)
{
uint32_t offset = ir.increase_bound_by(2);
uint32_t type_ptr_id = offset;
uint32_t var_id = offset + 1;
// Create gl_WorkgroupSize.
uint32_t type_id = build_extended_vector_type(get_uint_type_id(), 3);
SPIRType uint_type_ptr = get<SPIRType>(type_id);
uint_type_ptr.pointer = true;
uint_type_ptr.pointer_depth++;
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, BuiltInWorkgroupSize);
builtin_workgroup_size_id = var_id;
mark_implicit_builtin(StorageClassInput, BuiltInWorkgroupSize, 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 (needs_buffer_size_buffer())
{
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;
}
if (!buffers_requiring_dynamic_offset.empty())
{
uint32_t var_id = build_constant_uint_array_pointer();
set_name(var_id, "spvDynamicOffsets");
// This should never match anything.
set_decoration(var_id, DecorationDescriptorSet, ~(5u));
set_decoration(var_id, DecorationBinding, msl_options.dynamic_offsets_buffer_index);
set_extended_decoration(var_id, SPIRVCrossDecorationResourceIndexPrimary,
msl_options.dynamic_offsets_buffer_index);
dynamic_offsets_buffer_id = var_id;
}
// If we're returning a struct from a vertex-like entry point, we must return a position attribute.
bool need_position = (get_execution_model() == ExecutionModelVertex || is_tese_shader()) &&
!capture_output_to_buffer && !get_is_rasterization_disabled() &&
!active_output_builtins.get(BuiltInPosition);
if (need_position)
{
// If we can get away with returning void from entry point, we don't need to care.
// If there is at least one other stage output, we need to return [[position]],
// so we need to create one if it doesn't appear in the SPIR-V. Before adding the
// implicit variable, check if it actually exists already, but just has not been used
// or initialized, and if so, mark it as active, and do not create the implicit variable.
bool has_output = false;
ir.for_each_typed_id<SPIRVariable>([&](uint32_t, SPIRVariable &var) {
if (var.storage == StorageClassOutput && interface_variable_exists_in_entry_point(var.self))
{
has_output = true;
// Check if the var is the Position builtin
if (has_decoration(var.self, DecorationBuiltIn) && get_decoration(var.self, DecorationBuiltIn) == BuiltInPosition)
active_output_builtins.set(BuiltInPosition);
// If the var is a struct, check if any members is the Position builtin
auto &var_type = get_variable_element_type(var);
if (var_type.basetype == SPIRType::Struct)
{
auto mbr_cnt = var_type.member_types.size();
for (uint32_t mbr_idx = 0; mbr_idx < mbr_cnt; mbr_idx++)
{
auto builtin = BuiltInMax;
bool is_builtin = is_member_builtin(var_type, mbr_idx, &builtin);
if (is_builtin && builtin == BuiltInPosition)
active_output_builtins.set(BuiltInPosition);
}
}
}
});
need_position = has_output && !active_output_builtins.get(BuiltInPosition);
}
if (need_position)
{
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_Position.
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.pointer_depth++;
vec4_type_ptr.parent_type = type_id;
vec4_type_ptr.storage = StorageClassOutput;
auto &ptr_type = set<SPIRType>(type_ptr_id, vec4_type_ptr);
ptr_type.self = type_id;
set<SPIRVariable>(var_id, type_ptr_id, StorageClassOutput);
set_decoration(var_id, DecorationBuiltIn, BuiltInPosition);
mark_implicit_builtin(StorageClassOutput, BuiltInPosition, var_id);
}
}
// Checks if the specified builtin variable (e.g. gl_InstanceIndex) is marked as active.
// If not, it marks it as active and forces a recompilation.
// This might be used when the optimization of inactive builtins was too optimistic (e.g. when "spvOut" is emitted).
void CompilerMSL::ensure_builtin(spv::StorageClass storage, spv::BuiltIn builtin)
{
Bitset *active_builtins = nullptr;
switch (storage)
{
case StorageClassInput:
active_builtins = &active_input_builtins;
break;
case StorageClassOutput:
active_builtins = &active_output_builtins;
break;
default:
break;
}
// At this point, the specified builtin variable must have already been declared in the entry point.
// If not, mark as active and force recompile.
if (active_builtins != nullptr && !active_builtins->get(builtin))
{
active_builtins->set(builtin);
force_recompile();
}
}
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);
auto &var = get_entry_point().interface_variables;
if (find(begin(var), end(var), VariableID(id)) == end(var))
var.push_back(id);
}
uint32_t CompilerMSL::build_constant_uint_array_pointer()
{
uint32_t offset = ir.increase_bound_by(3);
uint32_t type_ptr_id = offset;
uint32_t type_ptr_ptr_id = offset + 1;
uint32_t var_id = offset + 2;
// Create a buffer to hold extra data, including the swizzle constants.
SPIRType uint_type_pointer = get_uint_type();
uint_type_pointer.pointer = true;
uint_type_pointer.pointer_depth++;
uint_type_pointer.parent_type = get_uint_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 (is_tessellating_triangles())
return "MTLTriangleTessellationFactorsHalf";
return "MTLQuadTessellationFactorsHalf";
}
SPIRType &CompilerMSL::get_uint_type()
{
return get<SPIRType>(get_uint_type_id());
}
uint32_t CompilerMSL::get_uint_type_id()
{
if (uint_type_id != 0)
return uint_type_id;
uint_type_id = ir.increase_bound_by(1);
SPIRType type;
type.basetype = SPIRType::UInt;
type.width = 32;
set<SPIRType>(uint_type_id, type);
return uint_type_id;
}
void CompilerMSL::emit_entry_point_declarations()
{
// FIXME: Get test coverage here ...
// Constant arrays of non-primitive types (i.e. matrices) won't link properly into Metal libraries
declare_complex_constant_arrays();
// 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), ")"));
}
// If we would emit no arguments, then omit the parentheses entirely. Otherwise,
// we'll wind up with a "most vexing parse" situation.
if (args.empty())
statement("constexpr sampler ",
type.basetype == SPIRType::SampledImage ? to_sampler_expression(samp.first) : to_name(samp.first),
";");
else
statement("constexpr sampler ",
type.basetype == SPIRType::SampledImage ? to_sampler_expression(samp.first) : to_name(samp.first),
"(", merge(args), ");");
}
// Emit dynamic buffers here.
for (auto &dynamic_buffer : buffers_requiring_dynamic_offset)
{
if (!dynamic_buffer.second.second)
{
// Could happen if no buffer was used at requested binding point.
continue;
}
const auto &var = get<SPIRVariable>(dynamic_buffer.second.second);
uint32_t var_id = var.self;
const auto &type = get_variable_data_type(var);
string name = to_name(var.self);
uint32_t desc_set = get_decoration(var.self, DecorationDescriptorSet);
uint32_t arg_id = argument_buffer_ids[desc_set];
uint32_t base_index = dynamic_buffer.second.first;
if (!type.array.empty())
{
// This is complicated, because we need to support arrays of arrays.
// And it's even worse if the outermost dimension is a runtime array, because now
// all this complicated goop has to go into the shader itself. (FIXME)
if (!type.array[type.array.size() - 1])
SPIRV_CROSS_THROW("Runtime arrays with dynamic offsets are not supported yet.");
else
{
is_using_builtin_array = true;
statement(get_argument_address_space(var), " ", type_to_glsl(type), "* ", to_restrict(var_id, true), name,
type_to_array_glsl(type), " =");
uint32_t dim = uint32_t(type.array.size());
uint32_t j = 0;
for (SmallVector<uint32_t> indices(type.array.size());
indices[type.array.size() - 1] < to_array_size_literal(type); j++)
{
while (dim > 0)
{
begin_scope();
--dim;
}
string arrays;
for (uint32_t i = uint32_t(type.array.size()); i; --i)
arrays += join("[", indices[i - 1], "]");
statement("(", get_argument_address_space(var), " ", type_to_glsl(type), "* ",
to_restrict(var_id, false), ")((", get_argument_address_space(var), " char* ",
to_restrict(var_id, false), ")", to_name(arg_id), ".", ensure_valid_name(name, "m"),
arrays, " + ", to_name(dynamic_offsets_buffer_id), "[", base_index + j, "]),");
while (++indices[dim] >= to_array_size_literal(type, dim) && dim < type.array.size() - 1)
{
end_scope(",");
indices[dim++] = 0;
}
}
end_scope_decl();
statement_no_indent("");
is_using_builtin_array = false;
}
}
else
{
statement(get_argument_address_space(var), " auto& ", to_restrict(var_id, true), name, " = *(",
get_argument_address_space(var), " ", type_to_glsl(type), "* ", to_restrict(var_id, false), ")((",
get_argument_address_space(var), " char* ", to_restrict(var_id, false), ")", to_name(arg_id), ".",
ensure_valid_name(name, "m"), " + ", to_name(dynamic_offsets_buffer_id), "[", base_index, "]);");
}
}
// Emit buffer arrays here.
for (uint32_t array_id : buffer_arrays_discrete)
{
const auto &var = get<SPIRVariable>(array_id);
const auto &type = get_variable_data_type(var);
const auto &buffer_type = get_variable_element_type(var);
string name = to_name(array_id);
statement(get_argument_address_space(var), " ", type_to_glsl(buffer_type), "* ", to_restrict(array_id, true), name,
"[] =");
begin_scope();
for (uint32_t i = 0; i < to_array_size_literal(type); ++i)
statement(name, "_", i, ",");
end_scope_decl();
statement_no_indent("");
}
// Discrete descriptors are processed in entry point emission every compiler iteration.
buffer_arrays_discrete.clear();
// Emit buffer aliases here.
for (auto &var_id : buffer_aliases_discrete)
{
const auto &var = get<SPIRVariable>(var_id);
const auto &type = get_variable_data_type(var);
auto addr_space = get_argument_address_space(var);
auto name = to_name(var_id);
uint32_t desc_set = get_decoration(var_id, DecorationDescriptorSet);
uint32_t desc_binding = get_decoration(var_id, DecorationBinding);
auto alias_name = join("spvBufferAliasSet", desc_set, "Binding", desc_binding);
statement(addr_space, " auto& ", to_restrict(var_id, true),
name,
" = *(", addr_space, " ", type_to_glsl(type), "*)", alias_name, ";");
}
// Discrete descriptors are processed in entry point emission every compiler iteration.
buffer_aliases_discrete.clear();
for (auto &var_pair : buffer_aliases_argument)
{
uint32_t var_id = var_pair.first;
uint32_t alias_id = var_pair.second;
const auto &var = get<SPIRVariable>(var_id);
const auto &type = get_variable_data_type(var);
auto addr_space = get_argument_address_space(var);
if (type.array.empty())
{
statement(addr_space, " auto& ", to_restrict(var_id, true), to_name(var_id), " = (", addr_space, " ",
type_to_glsl(type), "&)", ir.meta[alias_id].decoration.qualified_alias, ";");
}
else
{
const char *desc_addr_space = descriptor_address_space(var_id, var.storage, "thread");
// Esoteric type cast. Reference to array of pointers.
// Auto here defers to UBO or SSBO. The address space of the reference needs to refer to the
// address space of the argument buffer itself, which is usually constant, but can be const device for
// large argument buffers.
is_using_builtin_array = true;
statement(desc_addr_space, " auto& ", to_restrict(var_id, true), to_name(var_id), " = (", addr_space, " ",
type_to_glsl(type), "* ", desc_addr_space, " (&)",
type_to_array_glsl(type), ")", ir.meta[alias_id].decoration.qualified_alias, ";");
is_using_builtin_array = false;
}
}
// Emit disabled fragment outputs.
std::sort(disabled_frag_outputs.begin(), disabled_frag_outputs.end());
for (uint32_t var_id : disabled_frag_outputs)
{
auto &var = get<SPIRVariable>(var_id);
add_local_variable_name(var_id);
statement(variable_decl(var), ";");
var.deferred_declaration = false;
}
}
string CompilerMSL::compile()
{
replace_illegal_entry_point_names();
ir.fixup_reserved_names();
// 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 = "";
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.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.allow_truncated_access_chain = true;
backend.comparison_image_samples_scalar = true;
backend.native_pointers = true;
backend.nonuniform_qualifier = "";
backend.support_small_type_sampling_result = true;
backend.supports_empty_struct = true;
backend.support_64bit_switch = true;
// Allow Metal to use the array<T> template unless we force it off.
backend.can_return_array = !msl_options.force_native_arrays;
backend.array_is_value_type = !msl_options.force_native_arrays;
// Arrays which are part of buffer objects are never considered to be value types (just plain C-style).
backend.array_is_value_type_in_buffer_blocks = false;
backend.support_pointer_to_pointer = true;
backend.implicit_c_integer_promotion_rules = 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_anonymous_struct_names();
fixup_type_alias();
replace_illegal_names();
sync_entry_point_aliases_and_names();
build_function_control_flow_graphs_and_analyze();
update_active_builtins();
analyze_image_and_sampler_usage();
analyze_sampled_image_usage();
analyze_interlocked_resource_usage();
preprocess_op_codes();
build_implicit_builtins();
if (needs_manual_helper_invocation_updates() &&
(active_input_builtins.get(BuiltInHelperInvocation) || needs_helper_invocation))
{
string discard_expr =
join(builtin_to_glsl(BuiltInHelperInvocation, StorageClassInput), " = true, discard_fragment()");
backend.discard_literal = discard_expr;
backend.demote_literal = discard_expr;
}
else
{
backend.discard_literal = "discard_fragment()";
backend.demote_literal = "discard_fragment()";
}
fixup_image_load_store_access();
set_enabled_interface_variables(get_active_interface_variables());
if (msl_options.force_active_argument_buffer_resources)
activate_argument_buffer_resources();
if (swizzle_buffer_id)
add_active_interface_variable(swizzle_buffer_id);
if (buffer_size_buffer_id)
add_active_interface_variable(buffer_size_buffer_id);
if (view_mask_buffer_id)
add_active_interface_variable(view_mask_buffer_id);
if (dynamic_offsets_buffer_id)
add_active_interface_variable(dynamic_offsets_buffer_id);
if (builtin_layer_id)
add_active_interface_variable(builtin_layer_id);
if (builtin_dispatch_base_id && !msl_options.supports_msl_version(1, 2))
add_active_interface_variable(builtin_dispatch_base_id);
if (builtin_sample_mask_id)
add_active_interface_variable(builtin_sample_mask_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 (is_tese_shader())
patch_stage_in_var_id = add_interface_block(StorageClassInput, true);
if (is_tesc_shader())
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
{
reset(pass_count);
// 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_custom_templates();
emit_custom_functions();
emit_specialization_constants_and_structs();
emit_resources();
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\"");
}
// Before MSL 2.1 (2.2 for textures), Metal vertex functions that write to
// resources must disable rasterization and return void.
if ((preproc.uses_buffer_write && !msl_options.supports_msl_version(2, 1)) ||
(preproc.uses_image_write && !msl_options.supports_msl_version(2, 2)))
is_rasterization_disabled = true;
// Tessellation control shaders are run as compute functions in Metal, and so
// must capture their output to a buffer.
if (is_tesc_shader() || (get_execution_model() == ExecutionModelVertex && msl_options.vertex_for_tessellation))
{
is_rasterization_disabled = true;
capture_output_to_buffer = true;
}
if (preproc.needs_subgroup_invocation_id)
needs_subgroup_invocation_id = true;
if (preproc.needs_subgroup_size)
needs_subgroup_size = true;
// build_implicit_builtins() hasn't run yet, and in fact, this needs to execute
// before then so that gl_SampleID will get added; so we also need to check if
// that function would add gl_FragCoord.
if (preproc.needs_sample_id || msl_options.force_sample_rate_shading ||
(is_sample_rate() && (active_input_builtins.get(BuiltInFragCoord) ||
(need_subpass_input_ms && !msl_options.use_framebuffer_fetch_subpasses))))
needs_sample_id = true;
if (preproc.needs_helper_invocation)
needs_helper_invocation = true;
// OpKill is removed by the parser, so we need to identify those by inspecting
// blocks.
ir.for_each_typed_id<SPIRBlock>([&preproc](uint32_t, SPIRBlock &block) {
if (block.terminator == SPIRBlock::Kill)
preproc.uses_discard = true;
});
// Fragment shaders that both write to storage resources and discard fragments
// need checks on the writes, to work around Metal allowing these writes despite
// the fragment being dead.
if (msl_options.check_discarded_frag_stores && preproc.uses_discard &&
(preproc.uses_buffer_write || preproc.uses_image_write))
{
frag_shader_needs_discard_checks = true;
needs_helper_invocation = true;
// Fragment discard store checks imply manual HelperInvocation updates.
msl_options.manual_helper_invocation_updates = true;
}
if (is_intersection_query())
{
add_header_line("#if __METAL_VERSION__ >= 230");
add_header_line("#include <metal_raytracing>");
add_header_line("using namespace metal::raytracing;");
add_header_line("#endif");
}
}
// 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) {
// Some builtins resolve directly to a function call which does not need any declared variables.
// Skip these.
if (var.storage == StorageClassInput && has_decoration(var.self, DecorationBuiltIn))
{
auto bi_type = BuiltIn(get_decoration(var.self, DecorationBuiltIn));
if (bi_type == BuiltInHelperInvocation && !needs_manual_helper_invocation_updates())
return;
if (bi_type == BuiltInHelperInvocation && needs_manual_helper_invocation_updates())
{
if (msl_options.is_ios() && !msl_options.supports_msl_version(2, 3))
SPIRV_CROSS_THROW("simd_is_helper_thread() requires version 2.3 on iOS.");
else if (msl_options.is_macos() && !msl_options.supports_msl_version(2, 1))
SPIRV_CROSS_THROW("simd_is_helper_thread() requires version 2.1 on macOS.");
// Make sure this is declared and initialized.
// Force this to have the proper name.
set_name(var.self, builtin_to_glsl(BuiltInHelperInvocation, StorageClassInput));
auto &entry_func = this->get<SPIRFunction>(ir.default_entry_point);
entry_func.add_local_variable(var.self);
vars_needing_early_declaration.push_back(var.self);
entry_func.fixup_hooks_in.push_back([this, &var]()
{ statement(to_name(var.self), " = simd_is_helper_thread();"); });
}
}
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);
// Use Metal's native frame-buffer fetch API for subpass inputs.
auto &type = get<SPIRType>(ops[0]);
if (type.basetype == SPIRType::Image && type.image.dim == DimSubpassData &&
(!msl_options.use_framebuffer_fetch_subpasses))
{
// Implicitly reads gl_FragCoord.
assert(builtin_frag_coord_id != 0);
added_arg_ids.insert(builtin_frag_coord_id);
if (msl_options.multiview)
{
// Implicitly reads gl_ViewIndex.
assert(builtin_view_idx_id != 0);
added_arg_ids.insert(builtin_view_idx_id);
}
else if (msl_options.arrayed_subpass_input)
{
// Implicitly reads gl_Layer.
assert(builtin_layer_id != 0);
added_arg_ids.insert(builtin_layer_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);
uint32_t rvalue_id = ops[1];
if (global_var_ids.find(rvalue_id) != global_var_ids.end())
added_arg_ids.insert(rvalue_id);
if (needs_frag_discard_checks())
added_arg_ids.insert(builtin_helper_invocation_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;
}
case OpAtomicExchange:
case OpAtomicCompareExchange:
case OpAtomicStore:
case OpAtomicIIncrement:
case OpAtomicIDecrement:
case OpAtomicIAdd:
case OpAtomicFAddEXT:
case OpAtomicISub:
case OpAtomicSMin:
case OpAtomicUMin:
case OpAtomicSMax:
case OpAtomicUMax:
case OpAtomicAnd:
case OpAtomicOr:
case OpAtomicXor:
case OpImageWrite:
if (needs_frag_discard_checks())
added_arg_ids.insert(builtin_helper_invocation_id);
break;
// Emulate texture2D atomic operations
case OpImageTexelPointer:
{
// When using the pointer, we need to know which variable it is actually loaded from.
uint32_t base_id = ops[2];
auto *var = maybe_get_backing_variable(base_id);
if (var && atomic_image_vars.count(var->self))
{
if (global_var_ids.find(base_id) != global_var_ids.end())
added_arg_ids.insert(base_id);
}
break;
}
case OpExtInst:
{
uint32_t extension_set = ops[2];
if (get<SPIRExtension>(extension_set).ext == SPIRExtension::GLSL)
{
auto op_450 = static_cast<GLSLstd450>(ops[3]);
switch (op_450)
{
case GLSLstd450InterpolateAtCentroid:
case GLSLstd450InterpolateAtSample:
case GLSLstd450InterpolateAtOffset:
{
// For these, we really need the stage-in block. It is theoretically possible to pass the
// interpolant object, but a) doing so would require us to create an entirely new variable
// with Interpolant type, and b) if we have a struct or array, handling all the members and
// elements could get unwieldy fast.
added_arg_ids.insert(stage_in_var_id);
break;
}
case GLSLstd450Modf:
case GLSLstd450Frexp:
{
uint32_t base_id = ops[5];
if (global_var_ids.find(base_id) != global_var_ids.end())
added_arg_ids.insert(base_id);
break;
}
default:
break;
}
}
break;
}
case OpGroupNonUniformInverseBallot:
{
added_arg_ids.insert(builtin_subgroup_invocation_id_id);
break;
}
case OpGroupNonUniformBallotFindLSB:
case OpGroupNonUniformBallotFindMSB:
{
added_arg_ids.insert(builtin_subgroup_size_id);
break;
}
case OpGroupNonUniformBallotBitCount:
{
auto operation = static_cast<GroupOperation>(ops[3]);
switch (operation)
{
case GroupOperationReduce:
added_arg_ids.insert(builtin_subgroup_size_id);
break;
case GroupOperationInclusiveScan:
case GroupOperationExclusiveScan:
added_arg_ids.insert(builtin_subgroup_invocation_id_id);
break;
default:
break;
}
break;
}
case OpDemoteToHelperInvocation:
if (needs_manual_helper_invocation_updates() &&
(active_input_builtins.get(BuiltInHelperInvocation) || needs_helper_invocation))
added_arg_ids.insert(builtin_helper_invocation_id);
break;
case OpIsHelperInvocationEXT:
if (needs_manual_helper_invocation_updates())
added_arg_ids.insert(builtin_helper_invocation_id);
break;
case OpRayQueryInitializeKHR:
case OpRayQueryProceedKHR:
case OpRayQueryTerminateKHR:
case OpRayQueryGenerateIntersectionKHR:
case OpRayQueryConfirmIntersectionKHR:
{
// Ray query accesses memory directly, need check pass down object if using Private storage class.
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 OpRayQueryGetRayTMinKHR:
case OpRayQueryGetRayFlagsKHR:
case OpRayQueryGetWorldRayOriginKHR:
case OpRayQueryGetWorldRayDirectionKHR:
case OpRayQueryGetIntersectionCandidateAABBOpaqueKHR:
case OpRayQueryGetIntersectionTypeKHR:
case OpRayQueryGetIntersectionTKHR:
case OpRayQueryGetIntersectionInstanceCustomIndexKHR:
case OpRayQueryGetIntersectionInstanceIdKHR:
case OpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR:
case OpRayQueryGetIntersectionGeometryIndexKHR:
case OpRayQueryGetIntersectionPrimitiveIndexKHR:
case OpRayQueryGetIntersectionBarycentricsKHR:
case OpRayQueryGetIntersectionFrontFaceKHR:
case OpRayQueryGetIntersectionObjectRayDirectionKHR:
case OpRayQueryGetIntersectionObjectRayOriginKHR:
case OpRayQueryGetIntersectionObjectToWorldKHR:
case OpRayQueryGetIntersectionWorldToObjectKHR:
{
// Ray query accesses memory directly, need check pass down object if using Private storage class.
uint32_t base_id = ops[2];
if (global_var_ids.find(base_id) != global_var_ids.end())
added_arg_ids.insert(base_id);
break;
}
default:
break;
}
if (needs_manual_helper_invocation_updates() && b.terminator == SPIRBlock::Kill &&
(active_input_builtins.get(BuiltInHelperInvocation) || needs_helper_invocation))
added_arg_ids.insert(builtin_helper_invocation_id);
// 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 control_point_added_in = false;
bool control_point_added_out = false;
bool patch_added_in = false;
bool patch_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));
bool is_patch = has_decoration(arg_id, DecorationPatch) || is_patch_block(*p_type);
bool is_block = has_decoration(p_type->self, DecorationBlock);
bool is_control_point_storage =
!is_patch && ((is_tessellation_shader() && var.storage == StorageClassInput) ||
(is_tesc_shader() && var.storage == StorageClassOutput));
bool is_patch_block_storage = is_patch && is_block && var.storage == StorageClassOutput;
bool is_builtin = is_builtin_variable(var);
bool variable_is_stage_io =
!is_builtin || bi_type == BuiltInPosition || bi_type == BuiltInPointSize ||
bi_type == BuiltInClipDistance || bi_type == BuiltInCullDistance ||
p_type->basetype == SPIRType::Struct;
bool is_redirected_to_global_stage_io = (is_control_point_storage || is_patch_block_storage) &&
variable_is_stage_io;
// If output is masked it is not considered part of the global stage IO interface.
if (is_redirected_to_global_stage_io && var.storage == StorageClassOutput)
is_redirected_to_global_stage_io = !is_stage_output_variable_masked(var);
if (is_redirected_to_global_stage_io)
{
// Tessellation control shaders see inputs and per-point outputs as arrays.
// Similarly, tessellation evaluation shaders see per-point inputs as arrays.
// We collected them into a structure; we must pass the array of this
// structure to the function.
std::string name;
if (is_patch)
name = var.storage == StorageClassInput ? patch_stage_in_var_name : patch_stage_out_var_name;
else
name = var.storage == StorageClassInput ? "gl_in" : "gl_out";
if (var.storage == StorageClassOutput && has_decoration(p_type->self, DecorationBlock))
{
// If we're redirecting a block, we might still need to access the original block
// variable if we're masking some members.
for (uint32_t mbr_idx = 0; mbr_idx < uint32_t(p_type->member_types.size()); mbr_idx++)
{
if (is_stage_output_block_member_masked(var, mbr_idx, true))
{
func.add_parameter(var.basetype, var.self, true);
break;
}
}
}
if (var.storage == StorageClassInput)
{
auto &added_in = is_patch ? patch_added_in : control_point_added_in;
if (added_in)
continue;
arg_id = is_patch ? patch_stage_in_var_id : stage_in_ptr_var_id;
added_in = true;
}
else if (var.storage == StorageClassOutput)
{
auto &added_out = is_patch ? patch_added_out : control_point_added_out;
if (added_out)
continue;
arg_id = is_patch ? patch_stage_out_var_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);
if (is_tese_shader() && msl_options.raw_buffer_tese_input && var.storage == StorageClassInput)
set_decoration(next_id, DecorationNonWritable);
}
else if (is_builtin && has_decoration(p_type->self, DecorationBlock))
{
// 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;
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.pointer_depth++;
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 new variable has all the same meta info
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 (var.storage == StorageClassWorkgroup)
{
auto *type = &this->get<SPIRType>(var.basetype);
if (type->basetype == SPIRType::Struct)
mark_as_workgroup_struct(*type);
}
});
// Physical storage buffer pointers can appear outside of the context of a variable, if the address
// is calculated from a ulong or uvec2 and cast to a pointer, so check if they need to be packed too.
ir.for_each_typed_id<SPIRType>([&](uint32_t, SPIRType &type) {
if (type.basetype == SPIRType::Struct && type.pointer && type.storage == StorageClassPhysicalStorageBuffer)
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;
}
// Handle possible recursion when a struct contains a pointer to its own type nested somewhere.
if (type.basetype == SPIRType::Struct && !has_extended_decoration(type.self, SPIRVCrossDecorationBufferBlockRepacked))
{
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 the specified type is a struct, it and any nested structs
// are marked as used with workgroup storage using the SPIRVCrossDecorationWorkgroupStruct decoration.
void CompilerMSL::mark_as_workgroup_struct(SPIRType &type)
{
// If this is not the base type (eg. it's a pointer or array), tunnel down
if (type.parent_type)
{
mark_as_workgroup_struct(get<SPIRType>(type.parent_type));
return;
}
// Handle possible recursion when a struct contains a pointer to its own type nested somewhere.
if (type.basetype == SPIRType::Struct && !has_extended_decoration(type.self, SPIRVCrossDecorationWorkgroupStruct))
{
set_extended_decoration(type.self, SPIRVCrossDecorationWorkgroupStruct);
// 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_workgroup_struct(mbr_type);
if (mbr_type.type_alias)
{
auto &mbr_type_alias = get<SPIRType>(mbr_type.type_alias);
mark_as_workgroup_struct(mbr_type_alias);
}
}
}
}
// If a shader input exists at the location, it is marked as being used by this shader
void CompilerMSL::mark_location_as_used_by_shader(uint32_t location, const SPIRType &type,
StorageClass storage, bool fallback)
{
uint32_t count = type_to_location_count(type);
switch (storage)
{
case StorageClassInput:
for (uint32_t i = 0; i < count; i++)
{
location_inputs_in_use.insert(location + i);
if (fallback)
location_inputs_in_use_fallback.insert(location + i);
}
break;
case StorageClassOutput:
for (uint32_t i = 0; i < count; i++)
{
location_outputs_in_use.insert(location + i);
if (fallback)
location_outputs_in_use_fallback.insert(location + i);
}
break;
default:
return;
}
}
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, SPIRType::BaseType basetype)
{
uint32_t new_type_id = ir.increase_bound_by(1);
auto &old_type = get<SPIRType>(type_id);
auto *type = &set<SPIRType>(new_type_id, old_type);
type->vecsize = components;
if (basetype != SPIRType::Unknown)
type->basetype = basetype;
type->self = new_type_id;
type->parent_type = type_id;
type->array.clear();
type->array_size_literal.clear();
type->pointer = false;
if (is_array(old_type))
{
uint32_t array_type_id = ir.increase_bound_by(1);
type = &set<SPIRType>(array_type_id, *type);
type->parent_type = new_type_id;
type->array = old_type.array;
type->array_size_literal = old_type.array_size_literal;
new_type_id = array_type_id;
}
if (old_type.pointer)
{
uint32_t ptr_type_id = ir.increase_bound_by(1);
type = &set<SPIRType>(ptr_type_id, *type);
type->self = new_type_id;
type->parent_type = new_type_id;
type->storage = old_type.storage;
type->pointer = true;
type->pointer_depth++;
new_type_id = ptr_type_id;
}
return new_type_id;
}
uint32_t CompilerMSL::build_msl_interpolant_type(uint32_t type_id, bool is_noperspective)
{
uint32_t new_type_id = ir.increase_bound_by(1);
SPIRType &type = set<SPIRType>(new_type_id, get<SPIRType>(type_id));
type.basetype = SPIRType::Interpolant;
type.parent_type = type_id;
// In Metal, the pull-model interpolant type encodes perspective-vs-no-perspective in the type itself.
// Add this decoration so we know which argument to pass to the template.
if (is_noperspective)
set_decoration(new_type_id, DecorationNoPerspective);
return new_type_id;
}
bool CompilerMSL::add_component_variable_to_interface_block(spv::StorageClass storage, const std::string &ib_var_ref,
SPIRVariable &var,
const SPIRType &type,
InterfaceBlockMeta &meta)
{
// Deal with Component decorations.
const InterfaceBlockMeta::LocationMeta *location_meta = nullptr;
uint32_t location = ~0u;
if (has_decoration(var.self, DecorationLocation))
{
location = get_decoration(var.self, DecorationLocation);
auto location_meta_itr = meta.location_meta.find(location);
if (location_meta_itr != end(meta.location_meta))
location_meta = &location_meta_itr->second;
}
// Check if we need to pad fragment output to match a certain number of components.
if (location_meta)
{
bool pad_fragment_output = has_decoration(var.self, DecorationLocation) &&
msl_options.pad_fragment_output_components &&
get_entry_point().model == ExecutionModelFragment && storage == StorageClassOutput;
auto &entry_func = get<SPIRFunction>(ir.default_entry_point);
uint32_t start_component = get_decoration(var.self, DecorationComponent);
uint32_t type_components = type.vecsize;
uint32_t num_components = location_meta->num_components;
if (pad_fragment_output)
{
uint32_t locn = get_decoration(var.self, DecorationLocation);
num_components = max<uint32_t>(num_components, get_target_components_for_fragment_location(locn));
}
// We have already declared an IO block member as m_location_N.
// Just emit an early-declared variable and fixup as needed.
// Arrays need to be unrolled here since each location might need a different number of components.
entry_func.add_local_variable(var.self);
vars_needing_early_declaration.push_back(var.self);
if (var.storage == StorageClassInput)
{
entry_func.fixup_hooks_in.push_back([=, &type, &var]() {
if (!type.array.empty())
{
uint32_t array_size = to_array_size_literal(type);
for (uint32_t loc_off = 0; loc_off < array_size; loc_off++)
{
statement(to_name(var.self), "[", loc_off, "]", " = ", ib_var_ref,
".m_location_", location + loc_off,
vector_swizzle(type_components, start_component), ";");
}
}
else
{
statement(to_name(var.self), " = ", ib_var_ref, ".m_location_", location,
vector_swizzle(type_components, start_component), ";");
}
});
}
else
{
entry_func.fixup_hooks_out.push_back([=, &type, &var]() {
if (!type.array.empty())
{
uint32_t array_size = to_array_size_literal(type);
for (uint32_t loc_off = 0; loc_off < array_size; loc_off++)
{
statement(ib_var_ref, ".m_location_", location + loc_off,
vector_swizzle(type_components, start_component), " = ",
to_name(var.self), "[", loc_off, "];");
}
}
else
{
statement(ib_var_ref, ".m_location_", location,
vector_swizzle(type_components, start_component), " = ", to_name(var.self), ";");
}
});
}
return true;
}
else
return false;
}
void CompilerMSL::add_plain_variable_to_interface_block(StorageClass storage, const string &ib_var_ref,
SPIRType &ib_type, SPIRVariable &var, InterfaceBlockMeta &meta)
{
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 (meta.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;
bool padded_input = false;
uint32_t start_component = 0;
auto &entry_func = get<SPIRFunction>(ir.default_entry_point);
if (add_component_variable_to_interface_block(storage, ib_var_ref, var, type, meta))
return;
bool pad_fragment_output = has_decoration(var.self, DecorationLocation) &&
msl_options.pad_fragment_output_components &&
get_entry_point().model == ExecutionModelFragment && storage == StorageClassOutput;
if (pad_fragment_output)
{
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;
}
}
if (storage == StorageClassInput && pull_model_inputs.count(var.self))
ib_type.member_types.push_back(build_msl_interpolant_type(type_id, is_noperspective));
else
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;
// If using pull-model interpolation, need to add a call to the correct interpolation method.
if (storage == StorageClassInput && pull_model_inputs.count(var.self))
{
if (is_centroid)
qual_var_name += ".interpolate_at_centroid()";
else if (is_sample)
qual_var_name += join(".interpolate_at_sample(", to_expression(builtin_sample_id_id), ")");
else
qual_var_name += ".interpolate_at_center()";
}
if (padded_output || padded_input)
{
entry_func.add_local_variable(var.self);
vars_needing_early_declaration.push_back(var.self);
if (padded_output)
{
entry_func.fixup_hooks_out.push_back([=, &var]() {
statement(qual_var_name, vector_swizzle(type_components, start_component), " = ", to_name(var.self),
";");
});
}
else
{
entry_func.fixup_hooks_in.push_back([=, &var]() {
statement(to_name(var.self), " = ", qual_var_name, vector_swizzle(type_components, start_component),
";");
});
}
}
else if (!meta.strip_array)
ir.meta[var.self].decoration.qualified_alias = qual_var_name;
if (var.storage == StorageClassOutput && var.initializer != ID(0))
{
if (padded_output || padded_input)
{
entry_func.fixup_hooks_in.push_back(
[=, &var]() { statement(to_name(var.self), " = ", to_expression(var.initializer), ";"); });
}
else
{
if (meta.strip_array)
{
entry_func.fixup_hooks_in.push_back([=, &var]() {
uint32_t index = get_extended_decoration(var.self, SPIRVCrossDecorationInterfaceMemberIndex);
auto invocation = to_tesc_invocation_id();
statement(to_expression(stage_out_ptr_var_id), "[",
invocation, "].",
to_member_name(ib_type, index), " = ", to_expression(var.initializer), "[",
invocation, "];");
});
}
else
{
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);
uint32_t comp = get_decoration(var.self, DecorationComponent);
if (storage == StorageClassInput)
{
type_id = ensure_correct_input_type(var.basetype, locn, comp, 0, meta.strip_array);
var.basetype = type_id;
type_id = get_pointee_type_id(type_id);
if (meta.strip_array && is_array(get<SPIRType>(type_id)))
type_id = get<SPIRType>(type_id).parent_type;
if (pull_model_inputs.count(var.self))
ib_type.member_types[ib_mbr_idx] = build_msl_interpolant_type(type_id, is_noperspective);
else
ib_type.member_types[ib_mbr_idx] = type_id;
}
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
if (comp)
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationComponent, comp);
mark_location_as_used_by_shader(locn, get<SPIRType>(type_id), storage);
}
else if (is_builtin && is_tessellation_shader() && storage == StorageClassInput && inputs_by_builtin.count(builtin))
{
uint32_t locn = inputs_by_builtin[builtin].location;
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
mark_location_as_used_by_shader(locn, type, storage);
}
else if (is_builtin && capture_output_to_buffer && storage == StorageClassOutput && outputs_by_builtin.count(builtin))
{
uint32_t locn = outputs_by_builtin[builtin].location;
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
mark_location_as_used_by_shader(locn, type, storage);
}
if (get_decoration_bitset(var.self).get(DecorationComponent))
{
uint32_t component = get_decoration(var.self, DecorationComponent);
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationComponent, component);
}
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 (storage != StorageClassInput || !pull_model_inputs.count(var.self))
{
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,
InterfaceBlockMeta &meta)
{
auto &entry_func = get<SPIRFunction>(ir.default_entry_point);
auto &var_type = meta.strip_array ? get_variable_element_type(var) : get_variable_data_type(var);
uint32_t elem_cnt = 0;
if (add_component_variable_to_interface_block(storage, ib_var_ref, var, var_type, meta))
return;
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));
bool flatten_from_ib_var = false;
string flatten_from_ib_mbr_name;
if (storage == StorageClassOutput && is_builtin && builtin == BuiltInClipDistance)
{
// Also declare [[clip_distance]] attribute here.
uint32_t clip_array_mbr_idx = uint32_t(ib_type.member_types.size());
ib_type.member_types.push_back(get_variable_data_type_id(var));
set_member_decoration(ib_type.self, clip_array_mbr_idx, DecorationBuiltIn, BuiltInClipDistance);
flatten_from_ib_mbr_name = builtin_to_glsl(BuiltInClipDistance, StorageClassOutput);
set_member_name(ib_type.self, clip_array_mbr_idx, flatten_from_ib_mbr_name);
// When we flatten, we flatten directly from the "out" struct,
// not from a function variable.
flatten_from_ib_var = true;
if (!msl_options.enable_clip_distance_user_varying)
return;
}
else if (!meta.strip_array)
{
// Only flatten/unflatten IO composites for non-tessellation cases where arrays are not stripped.
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;
}
}
if (storage == StorageClassInput && pull_model_inputs.count(var.self))
ib_type.member_types.push_back(build_msl_interpolant_type(get_pointee_type_id(type_id), is_noperspective));
else
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;
uint32_t comp = get_decoration(var.self, DecorationComponent);
if (storage == StorageClassInput)
{
var.basetype = ensure_correct_input_type(var.basetype, locn, comp, 0, meta.strip_array);
uint32_t mbr_type_id = ensure_correct_input_type(usable_type->self, locn, comp, 0, meta.strip_array);
if (storage == StorageClassInput && pull_model_inputs.count(var.self))
ib_type.member_types[ib_mbr_idx] = build_msl_interpolant_type(mbr_type_id, is_noperspective);
else
ib_type.member_types[ib_mbr_idx] = mbr_type_id;
}
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
if (comp)
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationComponent, comp);
mark_location_as_used_by_shader(locn, *usable_type, storage);
}
else if (is_builtin && is_tessellation_shader() && storage == StorageClassInput && inputs_by_builtin.count(builtin))
{
uint32_t locn = inputs_by_builtin[builtin].location + i;
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
mark_location_as_used_by_shader(locn, *usable_type, storage);
}
else if (is_builtin && capture_output_to_buffer && storage == StorageClassOutput && outputs_by_builtin.count(builtin))
{
uint32_t locn = outputs_by_builtin[builtin].location + i;
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationLocation, locn);
mark_location_as_used_by_shader(locn, *usable_type, storage);
}
else if (is_builtin && (builtin == BuiltInClipDistance || builtin == BuiltInCullDistance))
{
// Declare the Clip/CullDistance as [[user(clip/cullN)]].
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationBuiltIn, builtin);
set_member_decoration(ib_type.self, ib_mbr_idx, DecorationIndex, i);
}
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);
}
if (storage != StorageClassInput || !pull_model_inputs.count(var.self))
{
// 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);
// Only flatten/unflatten IO composites for non-tessellation cases where arrays are not stripped.
if (!meta.strip_array)
{
switch (storage)
{
case StorageClassInput:
entry_func.fixup_hooks_in.push_back([=, &var]() {
if (pull_model_inputs.count(var.self))
{
string lerp_call;
if (is_centroid)
lerp_call = ".interpolate_at_centroid()";
else if (is_sample)
lerp_call = join(".interpolate_at_sample(", to_expression(builtin_sample_id_id), ")");
else
lerp_call = ".interpolate_at_center()";
statement(to_name(var.self), "[", i, "] = ", ib_var_ref, ".", mbr_name, lerp_call, ";");
}
else
{
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, "]")),
";");