MSL: Support SPV_KHR_multiview.

This is needed to support `VK_KHR_multiview`, which is in turn needed
for Vulkan 1.1 support. Unfortunately, Metal provides no native support
for this, and Apple is once again less than forthcoming, so we have to
implement it all ourselves.

Tessellation and geometry shaders are deliberately unsupported for now.
The problem is that the current implementation encodes the `ViewIndex`
as part of the `InstanceIndex`, which in the SPIR-V environment at least
only exists in the vertex shader. So we need to work out a way to pass
the view index along to the later stages.

This implementation runs vertex shaders for all views up to the highest
bit set in the view mask, even those whose bits are clear. The fragments
for the inactive views are then discarded. Avoiding this is difficult:
calculating the view indices becomes far more complicated if we can only
run for those views which are set in the mask.
This commit is contained in:
Chip Davis 2019-05-31 12:06:20 -05:00
parent 8ee8e60f70
commit 7eecf5a46b
13 changed files with 372 additions and 7 deletions

View File

@ -514,6 +514,7 @@ struct CLIArguments
bool msl_domain_lower_left = false;
bool msl_argument_buffers = false;
bool msl_texture_buffer_native = false;
bool msl_multiview = false;
bool glsl_emit_push_constant_as_ubo = false;
bool glsl_emit_ubo_as_plain_uniforms = false;
bool emit_line_directives = false;
@ -592,6 +593,7 @@ static void print_help()
"\t[--msl-argument-buffers]\n"
"\t[--msl-texture-buffer-native]\n"
"\t[--msl-discrete-descriptor-set <index>]\n"
"\t[--msl-multiview]\n"
"\t[--hlsl]\n"
"\t[--reflect]\n"
"\t[--shader-model]\n"
@ -750,6 +752,7 @@ static string compile_iteration(const CLIArguments &args, std::vector<uint32_t>
msl_opts.tess_domain_origin_lower_left = args.msl_domain_lower_left;
msl_opts.argument_buffers = args.msl_argument_buffers;
msl_opts.texture_buffer_native = args.msl_texture_buffer_native;
msl_opts.multiview = args.msl_multiview;
msl_comp->set_msl_options(msl_opts);
for (auto &v : args.msl_discrete_descriptor_sets)
msl_comp->add_discrete_descriptor_set(v);
@ -1069,6 +1072,7 @@ static int main_inner(int argc, char *argv[])
cbs.add("--msl-discrete-descriptor-set",
[&args](CLIParser &parser) { args.msl_discrete_descriptor_sets.push_back(parser.next_uint()); });
cbs.add("--msl-texture-buffer-native", [&args](CLIParser &) { args.msl_texture_buffer_native = true; });
cbs.add("--msl-multiview", [&args](CLIParser &) { args.msl_multiview = true; });
cbs.add("--extension", [&args](CLIParser &parser) { args.extensions.push_back(parser.next_string()); });
cbs.add("--rename-entry-point", [&args](CLIParser &parser) {
auto old_name = parser.next_string();

View File

@ -0,0 +1,32 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct main0_out
{
float4 FragColor [[color(0)]];
};
struct main0_in
{
float4 vColor [[user(locn0)]];
float2 vTex_0 [[user(locn1)]];
float2 vTex_1 [[user(locn2)]];
float2 vTex_2 [[user(locn3)]];
float2 vTex_3 [[user(locn4)]];
};
fragment main0_out main0(main0_in in [[stage_in]], constant uint* spvViewMask [[buffer(24)]], texture2d<float> uTex [[texture(0)]], sampler uTexSmplr [[sampler(0)]], uint gl_ViewIndex [[render_target_array_index]])
{
main0_out out = {};
float2 vTex[4] = {};
vTex[0] = in.vTex_0;
vTex[1] = in.vTex_1;
vTex[2] = in.vTex_2;
vTex[3] = in.vTex_3;
gl_ViewIndex += spvViewMask[0];
out.FragColor = in.vColor * uTex.sample(uTexSmplr, vTex[int(gl_ViewIndex)]);
return out;
}

View File

@ -0,0 +1,31 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct MVPs
{
float4x4 MVP[2];
};
struct main0_out
{
float4 gl_Position [[position]];
uint gl_Layer [[render_target_array_index]];
};
struct main0_in
{
float4 Position [[attribute(0)]];
};
vertex main0_out main0(main0_in in [[stage_in]], constant uint* spvViewMask [[buffer(24)]], constant MVPs& _19 [[buffer(0)]], uint gl_InstanceIndex [[instance_id]])
{
main0_out out = {};
uint gl_ViewIndex = spvViewMask[0] + gl_InstanceIndex % spvViewMask[1];
gl_InstanceIndex /= spvViewMask[1];
out.gl_Position = _19.MVP[int(gl_ViewIndex)] * in.Position;
out.gl_Layer = gl_ViewIndex - spvViewMask[0];
return out;
}

View File

@ -0,0 +1,29 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct MVPs
{
float4x4 MVP[2];
};
struct main0_out
{
float4 gl_Position [[position]];
uint gl_Layer [[render_target_array_index]];
};
struct main0_in
{
float4 Position [[attribute(0)]];
};
vertex main0_out main0(main0_in in [[stage_in]], constant MVPs& _19 [[buffer(0)]], uint gl_InstanceIndex [[instance_id]])
{
main0_out out = {};
const uint gl_ViewIndex = 0;
out.gl_Position = _19.MVP[int(gl_ViewIndex)] * in.Position;
return out;
}

View File

@ -0,0 +1,32 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct main0_out
{
float4 FragColor [[color(0)]];
};
struct main0_in
{
float4 vColor [[user(locn0)]];
float2 vTex_0 [[user(locn1)]];
float2 vTex_1 [[user(locn2)]];
float2 vTex_2 [[user(locn3)]];
float2 vTex_3 [[user(locn4)]];
};
fragment main0_out main0(main0_in in [[stage_in]], constant uint* spvViewMask [[buffer(24)]], texture2d<float> uTex [[texture(0)]], sampler uTexSmplr [[sampler(0)]], uint gl_ViewIndex [[render_target_array_index]])
{
main0_out out = {};
float2 vTex[4] = {};
vTex[0] = in.vTex_0;
vTex[1] = in.vTex_1;
vTex[2] = in.vTex_2;
vTex[3] = in.vTex_3;
gl_ViewIndex += spvViewMask[0];
out.FragColor = in.vColor * uTex.sample(uTexSmplr, vTex[int(gl_ViewIndex)]);
return out;
}

View File

@ -0,0 +1,31 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct MVPs
{
float4x4 MVP[2];
};
struct main0_out
{
float4 gl_Position [[position]];
uint gl_Layer [[render_target_array_index]];
};
struct main0_in
{
float4 Position [[attribute(0)]];
};
vertex main0_out main0(main0_in in [[stage_in]], constant uint* spvViewMask [[buffer(24)]], constant MVPs& _19 [[buffer(0)]], uint gl_InstanceIndex [[instance_id]])
{
main0_out out = {};
uint gl_ViewIndex = spvViewMask[0] + gl_InstanceIndex % spvViewMask[1];
gl_InstanceIndex /= spvViewMask[1];
out.gl_Position = _19.MVP[int(gl_ViewIndex)] * in.Position;
out.gl_Layer = gl_ViewIndex - spvViewMask[0];
return out;
}

View File

@ -0,0 +1,29 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct MVPs
{
float4x4 MVP[2];
};
struct main0_out
{
float4 gl_Position [[position]];
uint gl_Layer [[render_target_array_index]];
};
struct main0_in
{
float4 Position [[attribute(0)]];
};
vertex main0_out main0(main0_in in [[stage_in]], constant MVPs& _19 [[buffer(0)]], uint gl_InstanceIndex [[instance_id]])
{
main0_out out = {};
const uint gl_ViewIndex = 0;
out.gl_Position = _19.MVP[int(gl_ViewIndex)] * in.Position;
return out;
}

View File

@ -0,0 +1,14 @@
#version 310 es
#extension GL_EXT_multiview : require
precision mediump float;
layout(location = 0) in vec4 vColor;
layout(location = 1) in vec2 vTex[4];
layout(binding = 0) uniform sampler2D uTex;
layout(location = 0) out vec4 FragColor;
void main()
{
FragColor = vColor * texture(uTex, vTex[gl_ViewIndex]);
}

View File

@ -0,0 +1,14 @@
#version 310 es
#extension GL_EXT_multiview : require
layout(std140, binding = 0) uniform MVPs
{
mat4 MVP[2];
};
layout(location = 0) in vec4 Position;
void main()
{
gl_Position = MVP[gl_ViewIndex] * Position;
}

View File

@ -0,0 +1,14 @@
#version 310 es
#extension GL_EXT_multiview : require
layout(std140, binding = 0) uniform MVPs
{
mat4 MVP[2];
};
layout(location = 0) in vec4 Position;
void main()
{
gl_Position = MVP[gl_ViewIndex] * Position;
}

View File

@ -105,8 +105,10 @@ void CompilerMSL::build_implicit_builtins()
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.multiview || active_input_builtins.get(BuiltInViewIndex));
if (need_subpass_input || need_sample_pos || need_subgroup_mask || need_vertex_params || need_tesc_params ||
needs_subgroup_invocation_id)
need_multiview || needs_subgroup_invocation_id)
{
bool has_frag_coord = false;
bool has_sample_id = false;
@ -118,6 +120,7 @@ void CompilerMSL::build_implicit_builtins()
bool has_primitive_id = false;
bool has_subgroup_invocation_id = false;
bool has_subgroup_size = false;
bool has_view_idx = false;
ir.for_each_typed_id<SPIRVariable>([&](uint32_t, SPIRVariable &var) {
if (var.storage != StorageClassInput || !ir.meta[var.self].decoration.builtin)
@ -189,6 +192,22 @@ void CompilerMSL::build_implicit_builtins()
builtin_subgroup_size_id = var.self;
has_subgroup_size = true;
}
if (need_multiview)
{
if (builtin == BuiltInInstanceIndex)
{
// The view index here is derived from the instance index.
builtin_instance_idx_id = var.self;
has_instance_idx = true;
}
if (builtin == BuiltInViewIndex)
{
builtin_view_idx_id = var.self;
has_view_idx = true;
}
}
});
if (!has_frag_coord && need_subpass_input)
@ -246,7 +265,8 @@ void CompilerMSL::build_implicit_builtins()
mark_implicit_builtin(StorageClassInput, BuiltInSampleId, var_id);
}
if (need_vertex_params && (!has_vertex_idx || !has_base_vertex || !has_instance_idx || !has_base_instance))
if ((need_vertex_params && (!has_vertex_idx || !has_base_vertex || !has_instance_idx || !has_base_instance)) ||
(need_multiview && (!has_instance_idx || !has_view_idx)))
{
uint32_t offset = ir.increase_bound_by(2);
uint32_t type_id = offset;
@ -265,7 +285,7 @@ void CompilerMSL::build_implicit_builtins()
auto &ptr_type = set<SPIRType>(type_ptr_id, uint_type_ptr);
ptr_type.self = type_id;
if (!has_vertex_idx)
if (need_vertex_params && !has_vertex_idx)
{
uint32_t var_id = ir.increase_bound_by(1);
@ -276,7 +296,7 @@ void CompilerMSL::build_implicit_builtins()
mark_implicit_builtin(StorageClassInput, BuiltInVertexIndex, var_id);
}
if (!has_base_vertex)
if (need_vertex_params && !has_base_vertex)
{
uint32_t var_id = ir.increase_bound_by(1);
@ -287,7 +307,7 @@ void CompilerMSL::build_implicit_builtins()
mark_implicit_builtin(StorageClassInput, BuiltInBaseVertex, var_id);
}
if (!has_instance_idx)
if (!has_instance_idx) // Needed by both multiview and tessellation
{
uint32_t var_id = ir.increase_bound_by(1);
@ -296,9 +316,30 @@ void CompilerMSL::build_implicit_builtins()
set_decoration(var_id, DecorationBuiltIn, BuiltInInstanceIndex);
builtin_instance_idx_id = var_id;
mark_implicit_builtin(StorageClassInput, BuiltInInstanceIndex, var_id);
if (need_multiview)
{
// Multiview shaders are not allowed to write to gl_Layer, ostensibly because
// it is implicitly written from gl_ViewIndex, but we have to do that explicitly.
// Note that we can't just abuse gl_ViewIndex for this purpose: it's an input, but
// gl_Layer is an output in vertex-pipeline shaders.
uint32_t type_ptr_out_id = ir.increase_bound_by(2);
SPIRType uint_type_ptr_out;
uint_type_ptr_out = uint_type;
uint_type_ptr_out.pointer = true;
uint_type_ptr_out.parent_type = type_id;
uint_type_ptr_out.storage = StorageClassOutput;
auto &ptr_out_type = set<SPIRType>(type_ptr_out_id, uint_type_ptr_out);
ptr_out_type.self = type_id;
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 (!has_base_instance)
if (need_vertex_params && !has_base_instance)
{
uint32_t var_id = ir.increase_bound_by(1);
@ -308,6 +349,17 @@ void CompilerMSL::build_implicit_builtins()
builtin_base_instance_id = var_id;
mark_implicit_builtin(StorageClassInput, BuiltInBaseInstance, var_id);
}
if (need_multiview && !has_view_idx)
{
uint32_t var_id = ir.increase_bound_by(1);
// Create gl_ViewIndex.
set<SPIRVariable>(var_id, type_ptr_id, StorageClassInput);
set_decoration(var_id, DecorationBuiltIn, BuiltInViewIndex);
builtin_view_idx_id = var_id;
mark_implicit_builtin(StorageClassInput, BuiltInViewIndex, var_id);
}
}
if (need_tesc_params && (!has_invocation_id || !has_primitive_id))
@ -428,6 +480,17 @@ void CompilerMSL::build_implicit_builtins()
set_extended_decoration(var_id, SPIRVCrossDecorationResourceIndexPrimary, msl_options.buffer_size_buffer_index);
buffer_size_buffer_id = var_id;
}
if (needs_view_mask_buffer())
{
uint32_t var_id = build_constant_uint_array_pointer();
set_name(var_id, "spvViewMask");
// This should never match anything.
set_decoration(var_id, DecorationDescriptorSet, ~(4u));
set_decoration(var_id, DecorationBinding, msl_options.view_mask_buffer_index);
set_extended_decoration(var_id, SPIRVCrossDecorationResourceIndexPrimary, msl_options.view_mask_buffer_index);
view_mask_buffer_id = var_id;
}
}
void CompilerMSL::mark_implicit_builtin(StorageClass storage, BuiltIn builtin, uint32_t id)
@ -732,6 +795,10 @@ string CompilerMSL::compile()
active_interface_variables.insert(swizzle_buffer_id);
if (buffer_size_buffer_id)
active_interface_variables.insert(buffer_size_buffer_id);
if (view_mask_buffer_id)
active_interface_variables.insert(view_mask_buffer_id);
if (builtin_layer_id)
active_interface_variables.insert(builtin_layer_id);
// Create structs to hold input, output and uniform variables.
// Do output first to ensure out. is declared at top of entry function.
@ -5700,6 +5767,10 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in
{
switch (builtin)
{
case BuiltInViewIndex:
if (!msl_options.multiview)
break;
/* fallthrough */
case BuiltInFrontFacing:
case BuiltInPointCoord:
case BuiltInFragCoord:
@ -6087,7 +6158,9 @@ void CompilerMSL::entry_point_args_builtin(string &ep_args)
bi_type != BuiltInClipDistance && bi_type != BuiltInCullDistance && bi_type != BuiltInSubgroupEqMask &&
bi_type != BuiltInBaryCoordNV && bi_type != BuiltInBaryCoordNoPerspNV &&
bi_type != BuiltInSubgroupGeMask && bi_type != BuiltInSubgroupGtMask &&
bi_type != BuiltInSubgroupLeMask && bi_type != BuiltInSubgroupLtMask)
bi_type != BuiltInSubgroupLeMask && bi_type != BuiltInSubgroupLtMask &&
((get_execution_model() == ExecutionModelFragment && msl_options.multiview) ||
bi_type != BuiltInViewIndex))
{
if (!ep_args.empty())
ep_args += ", ";
@ -6582,6 +6655,44 @@ void CompilerMSL::fix_up_shader_inputs_outputs()
to_expression(builtin_subgroup_invocation_id_id), " - 32, 0)), uint2(0));");
});
break;
case BuiltInViewIndex:
if (!msl_options.multiview)
{
// According to the Vulkan spec, when not running under a multiview
// render pass, ViewIndex is 0.
entry_func.fixup_hooks_in.push_back([=]() {
statement("const ", builtin_type_decl(bi_type), " ", to_expression(var_id), " = 0;");
});
}
else if (get_execution_model() == ExecutionModelFragment)
{
// Because we adjusted the view index in the vertex shader, we have to
// adjust it back here.
entry_func.fixup_hooks_in.push_back([=]() {
statement(to_expression(var_id), " += ", to_expression(view_mask_buffer_id), "[0];");
});
}
else if (get_execution_model() == ExecutionModelVertex)
{
// Metal provides no special support for multiview, so we smuggle
// the view index in the instance index.
entry_func.fixup_hooks_in.push_back([=]() {
statement(builtin_type_decl(bi_type), " ", to_expression(var_id), " = ",
to_expression(view_mask_buffer_id), "[0] + ", to_expression(builtin_instance_idx_id),
" % ", to_expression(view_mask_buffer_id), "[1];");
statement(to_expression(builtin_instance_idx_id), " /= ", to_expression(view_mask_buffer_id),
"[1];");
});
// In addition to setting the variable itself, we also need to
// set the render_target_array_index with it on output. We have to
// offset this by the base view index, because Metal isn't in on
// our little game here.
entry_func.fixup_hooks_out.push_back([=]() {
statement(to_expression(builtin_layer_id), " = ", to_expression(var_id), " - ",
to_expression(view_mask_buffer_id), "[0];");
});
}
break;
default:
break;
}
@ -7883,6 +7994,12 @@ string CompilerMSL::builtin_qualifier(BuiltIn builtin)
case BuiltInSamplePosition:
// Shouldn't be reached.
SPIRV_CROSS_THROW("Sample position is retrieved by a function in MSL.");
case BuiltInViewIndex:
if (execution.model != ExecutionModelFragment)
SPIRV_CROSS_THROW("ViewIndex is handled specially outside fragment shaders.");
// The ViewIndex was implicitly used in the prior stages to set the render_target_array_index,
// so we can get it from there.
return "render_target_array_index";
// Fragment function out
case BuiltInFragDepth:
@ -8050,6 +8167,8 @@ string CompilerMSL::builtin_type_decl(BuiltIn builtin, uint32_t id)
return "uint";
case BuiltInSamplePosition:
return "float2";
case BuiltInViewIndex:
return "uint";
// Fragment function out
case BuiltInFragDepth:
@ -8634,6 +8753,7 @@ void CompilerMSL::bitcast_from_builtin_load(uint32_t source_id, std::string &exp
case BuiltInPrimitiveId:
case BuiltInSubgroupSize:
case BuiltInSubgroupLocalInvocationId:
case BuiltInViewIndex:
expected_type = SPIRType::UInt;
break;
@ -8676,6 +8796,7 @@ void CompilerMSL::bitcast_to_builtin_store(uint32_t target_id, std::string &expr
case BuiltInViewportIndex:
case BuiltInFragStencilRefEXT:
case BuiltInPrimitiveId:
case BuiltInViewIndex:
expected_type = SPIRType::UInt;
break;

View File

@ -191,12 +191,14 @@ public:
uint32_t shader_patch_output_buffer_index = 27;
uint32_t shader_tess_factor_buffer_index = 26;
uint32_t buffer_size_buffer_index = 25;
uint32_t view_mask_buffer_index = 24;
uint32_t shader_input_wg_index = 0;
bool enable_point_size_builtin = true;
bool disable_rasterization = false;
bool capture_output_to_buffer = false;
bool swizzle_texture_samples = false;
bool tess_domain_origin_lower_left = false;
bool multiview = false;
// Enable use of MSL 2.0 indirect argument buffers.
// MSL 2.0 must also be enabled.
@ -268,6 +270,13 @@ public:
return !buffers_requiring_array_length.empty();
}
// Provide feedback to calling API to allow it to pass a buffer
// containing the view mask for the current multiview subpass.
bool needs_view_mask_buffer() const
{
return msl_options.multiview;
}
// Provide feedback to calling API to allow it to pass an output
// buffer if the shader needs it.
bool needs_output_buffer() const
@ -526,12 +535,15 @@ protected:
uint32_t builtin_base_vertex_id = 0;
uint32_t builtin_instance_idx_id = 0;
uint32_t builtin_base_instance_id = 0;
uint32_t builtin_view_idx_id = 0;
uint32_t builtin_layer_id = 0;
uint32_t builtin_invocation_id_id = 0;
uint32_t builtin_primitive_id_id = 0;
uint32_t builtin_subgroup_invocation_id_id = 0;
uint32_t builtin_subgroup_size_id = 0;
uint32_t swizzle_buffer_id = 0;
uint32_t buffer_size_buffer_id = 0;
uint32_t view_mask_buffer_id = 0;
void bitcast_to_builtin_store(uint32_t target_id, std::string &expr, const SPIRType &expr_type) override;
void bitcast_from_builtin_load(uint32_t source_id, std::string &expr, const SPIRType &expr_type) override;

View File

@ -203,6 +203,8 @@ def cross_compile_msl(shader, spirv, opt, iterations, paths):
msl_args.append('3')
if '.line.' in shader:
msl_args.append('--emit-line-directives')
if '.multiview.' in shader:
msl_args.append('--msl-multiview')
subprocess.check_call(msl_args)