MSL: Expand arrays of buffers passed as input.

Even as of Metal 2.1, MSL still doesn't support arrays of buffers
directly. Therefore, we must manually expand them. In the prologue, we
define arrays holding the argument pointers; these arrays are what the
transpiled code ends up referencing. We might be able to do similar
things for textures and samplers prior to MSL 2.0.

Speaking of which, also enable texture arrays on iOS MSL 1.2.
This commit is contained in:
Chip Davis 2018-09-26 20:06:05 -05:00
parent 69b034f26e
commit 3a9af9681c
12 changed files with 292 additions and 20 deletions

View File

@ -490,6 +490,7 @@ struct CLIArguments
bool sso = false;
bool support_nonzero_baseinstance = true;
bool msl_swizzle_texture_samples = false;
bool msl_ios = false;
vector<PLSArg> pls_in;
vector<PLSArg> pls_out;
vector<Remap> remaps;
@ -541,6 +542,8 @@ static void print_help()
"\t[--cpp-interface-name <name>]\n"
"\t[--msl]\n"
"\t[--msl-version <MMmmpp>]\n"
"\t[--msl-swizzle-texture-samples]\n"
"\t[--msl-ios]\n"
"\t[--hlsl]\n"
"\t[--reflect]\n"
"\t[--shader-model]\n"
@ -705,6 +708,7 @@ static int main_inner(int argc, char *argv[])
cbs.add("--flatten-multidimensional-arrays", [&args](CLIParser &) { args.flatten_multidimensional_arrays = true; });
cbs.add("--no-420pack-extension", [&args](CLIParser &) { args.use_420pack_extension = false; });
cbs.add("--msl-swizzle-texture-samples", [&args](CLIParser &) { args.msl_swizzle_texture_samples = true; });
cbs.add("--msl-ios", [&args](CLIParser &) { args.msl_ios = 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();
@ -825,6 +829,8 @@ static int main_inner(int argc, char *argv[])
if (args.set_msl_version)
msl_opts.msl_version = args.msl_version;
msl_opts.swizzle_texture_samples = args.msl_swizzle_texture_samples;
if (args.msl_ios)
msl_opts.platform = CompilerMSL::Options::iOS;
msl_comp->set_msl_options(msl_opts);
}
else if (args.hlsl)

View File

@ -0,0 +1,35 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct storage_block
{
uint4 baz;
int2 quux;
};
struct constant_block
{
float4 foo;
int bar;
};
vertex void main0(device storage_block* storage_0 [[buffer(0)]], device storage_block* storage_1 [[buffer(1)]], constant constant_block* constants_0 [[buffer(2)]], constant constant_block* constants_1 [[buffer(3)]], constant constant_block* constants_2 [[buffer(4)]], constant constant_block* constants_3 [[buffer(5)]], array<texture2d<int>, 3> images [[texture(0)]])
{
device storage_block* storage[] =
{
storage_0,
storage_1,
};
constant constant_block* constants[] =
{
constants_0,
constants_1,
constants_2,
constants_3,
};
storage[0]->baz = uint4(constants[3]->foo);
storage[1]->quux = images[2].read(uint2(int2(constants[1]->bar))).xy;
}

View File

@ -0,0 +1,35 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct storage_block
{
uint4 baz;
int2 quux;
};
struct constant_block
{
float4 foo;
int bar;
};
vertex void main0(device storage_block* storage_0 [[buffer(0)]], device storage_block* storage_1 [[buffer(1)]], constant constant_block* constants_0 [[buffer(2)]], constant constant_block* constants_1 [[buffer(3)]], constant constant_block* constants_2 [[buffer(4)]], constant constant_block* constants_3 [[buffer(5)]], array<texture2d<int>, 3> images [[texture(0)]])
{
device storage_block* storage[] =
{
storage_0,
storage_1,
};
constant constant_block* constants[] =
{
constants_0,
constants_1,
constants_2,
constants_3,
};
storage[0]->baz = uint4(constants[3]->foo);
storage[1]->quux = images[2].read(uint2(int2(constants[1]->bar))).xy;
}

View File

@ -0,0 +1,42 @@
#pragma clang diagnostic ignored "-Wmissing-prototypes"
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct storage_block
{
uint4 baz;
int2 quux;
};
struct constant_block
{
float4 foo;
int bar;
};
void doWork(device storage_block* (&storage)[2], constant constant_block* (&constants)[4], thread const array<texture2d<int>, 3> images)
{
storage[0]->baz = uint4(constants[3]->foo);
storage[1]->quux = images[2].read(uint2(int2(constants[1]->bar))).xy;
}
vertex void main0(device storage_block* storage_0 [[buffer(0)]], device storage_block* storage_1 [[buffer(1)]], constant constant_block* constants_0 [[buffer(2)]], constant constant_block* constants_1 [[buffer(3)]], constant constant_block* constants_2 [[buffer(4)]], constant constant_block* constants_3 [[buffer(5)]], array<texture2d<int>, 3> images [[texture(0)]])
{
device storage_block* storage[] =
{
storage_0,
storage_1,
};
constant constant_block* constants[] =
{
constants_0,
constants_1,
constants_2,
constants_3,
};
doWork(storage, constants, images);
}

View File

@ -0,0 +1,35 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct storage_block
{
uint4 baz;
int2 quux;
};
struct constant_block
{
float4 foo;
int bar;
};
vertex void main0(device storage_block* storage_0 [[buffer(0)]], device storage_block* storage_1 [[buffer(1)]], constant constant_block* constants_0 [[buffer(2)]], constant constant_block* constants_1 [[buffer(3)]], constant constant_block* constants_2 [[buffer(4)]], constant constant_block* constants_3 [[buffer(5)]], array<texture2d<int>, 3> images [[texture(0)]])
{
device storage_block* storage[] =
{
storage_0,
storage_1,
};
constant constant_block* constants[] =
{
constants_0,
constants_1,
constants_2,
constants_3,
};
storage[0]->baz = uint4(constants[3]->foo);
storage[1]->quux = images[2].read(uint2(int2(constants[1]->bar))).xy;
}

View File

@ -0,0 +1,26 @@
#version 450
layout(constant_id = 0) const int arraySize = 3;
layout(binding = 0, rgba32i) uniform iimage2D images[arraySize];
uniform constant_block
{
vec4 foo;
int bar;
} constants[4];
buffer storage_block
{
uvec4 baz;
ivec2 quux;
} storage[2];
void doWork()
{
storage[0].baz = uvec4(constants[3].foo);
storage[1].quux = imageLoad(images[2], ivec2(constants[1].bar)).xy;
}
void main()
{
doWork();
}

View File

@ -0,0 +1,21 @@
#version 450
layout(constant_id = 0) const int arraySize = 3;
layout(binding = 0, rgba32i) uniform iimage2D images[arraySize];
uniform constant_block
{
vec4 foo;
int bar;
} constants[4];
buffer storage_block
{
uvec4 baz;
ivec2 quux;
} storage[2];
void main()
{
storage[0].baz = uvec4(constants[3].foo);
storage[1].quux = imageLoad(images[2], ivec2(constants[1].bar)).xy;
}

View File

@ -5389,10 +5389,7 @@ string CompilerGLSL::access_chain_internal(uint32_t base, const uint32_t *indice
if (!qual_mbr_name.empty())
expr = qual_mbr_name;
else
{
expr += ".";
expr += to_member_name(*type, index);
}
expr += to_member_reference(maybe_get_backing_variable(base), *type, index);
}
is_packed = member_is_packed_type(*type, index);
@ -8233,6 +8230,11 @@ string CompilerGLSL::to_member_name(const SPIRType &type, uint32_t index)
return join("_m", index);
}
string CompilerGLSL::to_member_reference(const SPIRVariable *, const SPIRType &type, uint32_t index)
{
return join(".", to_member_name(type, index));
}
void CompilerGLSL::add_member_name(SPIRType &type, uint32_t index)
{
auto &memb = meta[type.self].members;
@ -9282,8 +9284,7 @@ void CompilerGLSL::branch(uint32_t from, uint32_t to)
// Only sensible solution is to make a ladder variable, which we declare at the top of the switch block,
// write to the ladder here, and defer the break.
// The loop we're breaking out of must dominate the switch block, or there is no ladder breaking case.
if (current_emitting_switch && is_loop_break(to) &&
current_emitting_switch->loop_dominator != -1u &&
if (current_emitting_switch && is_loop_break(to) && current_emitting_switch->loop_dominator != -1u &&
get<SPIRBlock>(current_emitting_switch->loop_dominator).merge_block == to)
{
if (!current_emitting_switch->need_ladder_break)

View File

@ -454,6 +454,7 @@ protected:
std::string enclose_expression(const std::string &expr);
void strip_enclosed_expression(std::string &expr);
std::string to_member_name(const SPIRType &type, uint32_t index);
virtual std::string to_member_reference(const SPIRVariable *var, const SPIRType &type, uint32_t index);
std::string type_to_glsl_constructor(const SPIRType &type);
std::string argument_decl(const SPIRFunction::Parameter &arg);
virtual std::string to_qualifiers_glsl(uint32_t id);

View File

@ -334,6 +334,20 @@ void CompilerMSL::emit_entry_point_declarations()
type.basetype == SPIRType::SampledImage ? to_sampler_expression(samp.first) : to_name(samp.first),
"(", merge(args), ");");
}
// Emit buffer arrays here.
for (uint32_t array_id : buffer_arrays)
{
const auto &var = get<SPIRVariable>(array_id);
const auto &type = get<SPIRType>(var.basetype);
string name = get_name(array_id);
statement(get_argument_address_space(var) + " " + type_to_glsl(type) + "* " + name + "[] =");
begin_scope();
for (uint32_t i = 0; i < type.array[0]; ++i)
statement(name + "_" + convert_to_string(i) + ",");
end_scope_decl();
}
buffer_arrays.clear();
}
string CompilerMSL::compile()
@ -3793,10 +3807,30 @@ string CompilerMSL::entry_point_args(bool append_comma)
auto &m = meta.at(type.self);
if (m.members.size() == 0)
break;
if (!ep_args.empty())
ep_args += ", ";
ep_args += get_argument_address_space(var) + " " + type_to_glsl(type) + "& " + r.name;
ep_args += " [[buffer(" + convert_to_string(r.index) + ")]]";
if (!type.array.empty())
{
// Metal doesn't directly support this, so we must expand the
// array. We'll declare a local array to hold these elements
// later.
uint32_t array_size =
type.array_size_literal.back() ? type.array.back() : get<SPIRConstant>(type.array.back()).scalar();
buffer_arrays.push_back(var_id);
for (uint32_t i = 0; i < array_size; ++i)
{
if (!ep_args.empty())
ep_args += ", ";
ep_args += get_argument_address_space(var) + " " + type_to_glsl(type) + "* " + r.name + "_" +
convert_to_string(i);
ep_args += " [[buffer(" + convert_to_string(r.index + i) + ")]]";
}
}
else
{
if (!ep_args.empty())
ep_args += ", ";
ep_args += get_argument_address_space(var) + " " + type_to_glsl(type) + "& " + r.name;
ep_args += " [[buffer(" + convert_to_string(r.index) + ")]]";
}
break;
}
case SPIRType::Sampler:
@ -3969,6 +4003,9 @@ string CompilerMSL::argument_decl(const SPIRFunction::Parameter &arg)
else if (is_array(type) && !type_is_image)
{
// Arrays of images and samplers are special cased.
if (get<SPIRVariable>(name_id).storage == StorageClassUniform ||
get<SPIRVariable>(name_id).storage == StorageClassStorageBuffer)
decl += "*";
decl += " (&";
decl += to_expression(name_id);
decl += ")";
@ -4088,6 +4125,15 @@ void CompilerMSL::replace_illegal_names()
CompilerGLSL::replace_illegal_names();
}
string CompilerMSL::to_member_reference(const SPIRVariable *var, const SPIRType &type, uint32_t index)
{
// If this is a buffer array, we have to dereference the buffer pointers.
if (var && (var->storage == StorageClassUniform || var->storage == StorageClassStorageBuffer) &&
!get<SPIRType>(var->basetype).array.empty())
return join("->", to_member_name(type, index));
return join(".", to_member_name(type, index));
}
string CompilerMSL::to_qualifiers_glsl(uint32_t id)
{
string quals;
@ -4204,7 +4250,13 @@ string CompilerMSL::image_type_glsl(const SPIRType &type, uint32_t id)
if (!type.array.empty())
{
if (!msl_options.supports_msl_version(2))
uint32_t major = 2, minor = 0;
if (msl_options.is_ios())
{
major = 1;
minor = 2;
}
if (!msl_options.supports_msl_version(major, minor))
SPIRV_CROSS_THROW("MSL 2.0 or greater is required for arrays of textures.");
// Arrays of images in MSL must be declared with a special array<T, N> syntax ala C++11 std::array.

View File

@ -323,6 +323,7 @@ protected:
std::string unpack_expression_type(std::string expr_str, const SPIRType &type) override;
std::string bitcast_glsl_op(const SPIRType &result_type, const SPIRType &argument_type) override;
bool skip_argument(uint32_t id) const override;
std::string to_member_reference(const SPIRVariable *var, const SPIRType &type, uint32_t index) override;
std::string to_qualifiers_glsl(uint32_t id) override;
void replace_illegal_names() override;
void declare_undefined_values() override;
@ -414,6 +415,7 @@ protected:
spv::Op previous_instruction_opcode = spv::OpNop;
std::unordered_map<uint32_t, MSLConstexprSampler> constexpr_samplers;
std::vector<uint32_t> buffer_arrays;
// OpcodeHandler that handles several MSL preprocessing operations.
struct OpCodePreprocessor : OpcodeHandler

View File

@ -87,14 +87,26 @@ def print_msl_compiler_version():
raise
def path_to_msl_standard(shader):
if '.msl2.' in shader:
return '-std=macos-metal2.0'
elif '.msl21.' in shader:
return '-std=macos-metal2.1'
elif '.msl11.' in shader:
return '-std=macos-metal1.1'
if '.ios.' in shader:
if '.msl2.' in shader:
return '-std=ios-metal2.0'
elif '.msl21.' in shader:
return '-std=ios-metal2.1'
elif '.msl11.' in shader:
return '-std=ios-metal1.1'
elif '.msl10.' in shader:
return '-std=ios-metal1.0'
else:
return '-std=ios-metal1.2'
else:
return '-std=macos-metal1.2'
if '.msl2.' in shader:
return '-std=macos-metal2.0'
elif '.msl21.' in shader:
return '-std=macos-metal2.1'
elif '.msl11.' in shader:
return '-std=macos-metal1.1'
else:
return '-std=macos-metal1.2'
def path_to_msl_standard_cli(shader):
if '.msl2.' in shader:
@ -109,8 +121,10 @@ def path_to_msl_standard_cli(shader):
def validate_shader_msl(shader, opt):
msl_path = reference_path(shader[0], shader[1], opt)
try:
msl_os = 'macosx'
# msl_os = 'iphoneos'
if '.ios.' in msl_path:
msl_os = 'iphoneos'
else:
msl_os = 'macosx'
subprocess.check_call(['xcrun', '--sdk', msl_os, 'metal', '-x', 'metal', path_to_msl_standard(msl_path), '-Werror', '-Wno-unused-variable', msl_path])
print('Compiled Metal shader: ' + msl_path) # display after so xcrun FNF is silent
except OSError as oe:
@ -139,6 +153,8 @@ def cross_compile_msl(shader, spirv, opt):
msl_args.append(path_to_msl_standard_cli(shader))
if '.swizzle.' in shader:
msl_args.append('--msl-swizzle-texture-samples')
if '.ios.' in shader:
msl_args.append('--msl-ios')
subprocess.check_call(msl_args)