CompilerMSL options access and UBO alignment test case.

CompilerMSL accesses options using same design pattern as CompilerGLSL and CompilerHLSL.
CompilerMSL support setting VA & rez binding specs via either constructor or compile() method overload.
CompilerMSL support single UBO packing and padding in single pass.
spriv_cross app (main.cpp) supports turning off UBO packing and padding via command line option.
Add MSL UBO alignment test shader.
This commit is contained in:
Bill Hollings 2017-03-12 17:42:51 -04:00
parent dc69427402
commit 5550c87b1f
6 changed files with 194 additions and 78 deletions

View File

@ -433,6 +433,7 @@ struct CLIArguments
uint32_t iterations = 1;
bool cpp = false;
bool metal = false;
bool msl_pack_ubos = true;
bool hlsl = false;
bool vulkan_semantics = false;
bool remove_unused = false;
@ -566,6 +567,7 @@ int main(int argc, char *argv[])
cbs.add("--cpp", [&args](CLIParser &) { args.cpp = true; });
cbs.add("--cpp-interface-name", [&args](CLIParser &parser) { args.cpp_interface_name = parser.next_string(); });
cbs.add("--metal", [&args](CLIParser &) { args.metal = true; });
cbs.add("--msl-no-pack-ubos", [&args](CLIParser &) { args.msl_pack_ubos = false; });
cbs.add("--hlsl", [&args](CLIParser &) { args.hlsl = true; });
cbs.add("--vulkan-semantics", [&args](CLIParser &) { args.vulkan_semantics = true; });
cbs.add("--extension", [&args](CLIParser &parser) { args.extensions.push_back(parser.next_string()); });
@ -631,7 +633,14 @@ int main(int argc, char *argv[])
static_cast<CompilerCPP *>(compiler.get())->set_interface_name(args.cpp_interface_name);
}
else if (args.metal)
{
compiler = unique_ptr<CompilerMSL>(new CompilerMSL(read_spirv_file(args.input)));
auto *msl_comp = static_cast<CompilerMSL *>(compiler.get());
auto msl_opts = msl_comp->get_options();
msl_opts.pad_and_pack_uniform_structs = args.msl_pack_ubos;
msl_comp->set_options(msl_opts);
}
else if (args.hlsl)
compiler = unique_ptr<CompilerHLSL>(new CompilerHLSL(read_spirv_file(args.input)));
else

View File

@ -10,7 +10,7 @@ struct UBO
float2 B1;
float C0;
float3 C1;
float3 D0;
packed_float3 D0;
float D1;
float E0;
float E1;
@ -40,7 +40,7 @@ vertex main0_out main0(constant UBO& _22 [[buffer(0)]])
out.oA = _22.A;
out.oB = float4(_22.B0, _22.B1);
out.oC = float4(_22.C0, _22.C1);
out.oD = float4(_22.D0, _22.D1);
out.oD = float4(float3(_22.D0), _22.D1);
out.oE = float4(_22.E0, _22.E1, _22.E2, _22.E3);
out.oF = float4(_22.F0, _22.F1, _22.F2);
return out;

View File

@ -0,0 +1,39 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct UBO
{
float4x4 mvp;
float2 targSize;
char pad2[8];
packed_float3 color;
float opacity;
};
struct main0_in
{
float4 aVertex [[attribute(0)]];
float3 aNormal [[attribute(1)]];
};
struct main0_out
{
float2 vSize [[user(locn0)]];
float3 vNormal [[user(locn1)]];
float3 vColor [[user(locn2)]];
float4 gl_Position [[position]];
float gl_PointSize;
};
vertex main0_out main0(main0_in in [[stage_in]], constant UBO& _18 [[buffer(0)]])
{
main0_out out = {};
out.gl_Position = _18.mvp * in.aVertex;
out.vNormal = in.aNormal;
out.vColor = float3(_18.color) * _18.opacity;
out.vSize = _18.targSize * _18.opacity;
return out;
}

View File

@ -0,0 +1,23 @@
#version 310 es
layout(binding = 0, std140) uniform UBO
{
mat4 mvp;
vec2 targSize;
vec3 color; // vec3 following vec2 should cause MSL to add pad if float3 is packed
float opacity; // Single float following vec3 should cause MSL float3 to pack
};
in vec4 aVertex;
in vec3 aNormal;
out vec3 vNormal;
out vec3 vColor;
out vec2 vSize;
void main()
{
gl_Position = mvp * aVertex;
vNormal = aNormal;
vColor = color * opacity;
vSize = targSize * opacity;
}

View File

@ -27,13 +27,20 @@ using namespace std;
static const uint32_t k_unknown_location = ~0;
CompilerMSL::CompilerMSL(vector<uint32_t> spirv_)
CompilerMSL::CompilerMSL(vector<uint32_t> spirv_, vector<MSLVertexAttr> *p_vtx_attrs,
std::vector<MSLResourceBinding> *p_res_bindings)
: CompilerGLSL(move(spirv_))
{
options.vertex.fixup_clipspace = false;
populate_func_name_overrides();
populate_var_name_overrides();
if (p_vtx_attrs)
for (auto &va : *p_vtx_attrs)
vtx_attrs_by_location[va.location] = &va;
if (p_res_bindings)
for (auto &rb : *p_res_bindings)
resource_bindings.push_back(&rb);
}
// Populate the collection of function names that need to be overridden
@ -49,32 +56,18 @@ void CompilerMSL::populate_var_name_overrides()
var_name_overrides["bias"] = "bias0";
}
string CompilerMSL::compile(MSLConfiguration &msl_cfg, vector<MSLVertexAttr> *p_vtx_attrs,
std::vector<MSLResourceBinding> *p_res_bindings)
string CompilerMSL::compile()
{
// Force a classic "C" locale, reverts when function returns
ClassicLocale classic_locale;
// Remember the input parameters
msl_config = msl_cfg;
// Set main function name if it was explicitly set
if (!msl_config.entry_point_name.empty())
set_name(entry_point, msl_config.entry_point_name);
vtx_attrs_by_location.clear();
if (p_vtx_attrs)
for (auto &va : *p_vtx_attrs)
vtx_attrs_by_location[va.location] = &va;
if (!options.entry_point_name.empty())
set_name(entry_point, options.entry_point_name);
non_stage_in_input_var_ids.clear();
struct_member_padding.clear();
resource_bindings.clear();
if (p_res_bindings)
for (auto &rb : *p_res_bindings)
resource_bindings.push_back(&rb);
// Preprocess OpCodes to extract the need to output additional header content
set_enabled_interface_variables(get_active_interface_variables());
preprocess_op_codes();
@ -90,8 +83,9 @@ string CompilerMSL::compile(MSLConfiguration &msl_cfg, vector<MSLVertexAttr> *p_
extract_global_variables_from_functions();
// Do not deal with GLES-isms like precision, older extensions and such.
options.es = false;
options.version = 120;
CompilerGLSL::options.es = false;
CompilerGLSL::options.version = 120;
CompilerGLSL::options.vertex.fixup_clipspace = false;
backend.float_literal_suffix = false;
backend.uint32_t_literal_suffix = true;
backend.basic_int_type = "int";
@ -125,10 +119,30 @@ string CompilerMSL::compile(MSLConfiguration &msl_cfg, vector<MSLVertexAttr> *p_
return buffer->str();
}
string CompilerMSL::compile()
string CompilerMSL::compile(vector<MSLVertexAttr> *p_vtx_attrs, std::vector<MSLResourceBinding> *p_res_bindings)
{
MSLConfiguration default_msl_cfg;
return compile(default_msl_cfg, nullptr, nullptr);
if (p_vtx_attrs)
{
vtx_attrs_by_location.clear();
for (auto &va : *p_vtx_attrs)
vtx_attrs_by_location[va.location] = &va;
}
if (p_res_bindings)
{
resource_bindings.clear();
for (auto &rb : *p_res_bindings)
resource_bindings.push_back(&rb);
}
return compile();
}
string CompilerMSL::compile(MSLConfiguration &msl_cfg, vector<MSLVertexAttr> *p_vtx_attrs,
std::vector<MSLResourceBinding> *p_res_bindings)
{
options = msl_cfg;
return compile(p_vtx_attrs, p_res_bindings);
}
// Register the need to output any custom functions.
@ -577,8 +591,10 @@ uint32_t CompilerMSL::get_input_buffer_block_var_id(uint32_t msl_buffer)
return ib_var_id;
}
// Sort the members of the struct type by offset, and pad & pack
// members where needed to align MSL members with SPIR-V offsets.
// Sort the members of the struct type by offset, and pack and then pad members where needed
// to align MSL members with SPIR-V offsets. The struct members are iterated twice. Packing
// occurs first, followed by padding, because packing a member reduces both its size and its
// natural alignment, possibly requiring a padding member to be added ahead of it.
void CompilerMSL::align_struct(SPIRType &ib_type)
{
uint32_t &ib_type_id = ib_type.self;
@ -590,42 +606,51 @@ void CompilerMSL::align_struct(SPIRType &ib_type)
uint32_t curr_offset = 0;
uint32_t mbr_cnt = uint32_t(ib_type.member_types.size());
// Test the alignment of each member, and if a member should be closer to the previous
// member than the default spacing expects, it is likely that the previous member is in
// a packed format. If so, and the previous member is packable, pack it.
// For example...this applies to any 3-element vector that is followed by a scalar.
for (uint32_t mbr_idx = 0; mbr_idx < mbr_cnt; mbr_idx++)
{
// Align current offset to the current member's default alignment.
size_t align_mask = get_declared_struct_member_alignment(ib_type, mbr_idx) - 1;
curr_offset = uint32_t((curr_offset + align_mask) & ~align_mask);
// Test member alignment and, if it needs to be adjusted, depending on which direction,
// either pad the current member by adding a dummy padding member that will be declared
// before the current member, or mark the previous member as packed, so that it will
// consume less space, and the current member can move forward.
// There is a situation where, in packing the previous member, the alignment of
// that member will change, necessitating the addition of a padding member before it.
// This is taken care of automatically because this compiler is multipass. The first
// pass will mark the packed member, and the second pass will insert a padding member.
// If we ever move to a single-pass design, this will break.
// Fetch the member offset as declared in the SPIRV.
uint32_t mbr_offset = get_member_decoration(ib_type_id, mbr_idx, DecorationOffset);
int32_t gap = (int32_t)mbr_offset - (int32_t)curr_offset;
if (gap > 0)
if (curr_offset > mbr_offset)
{
// Since MSL and SPIR-V have slightly different struct member alignment and
// size rules, we'll pad to standard C-packing rules. If the member is farther
// away than C-packing, expects, add an inert padding member before the the member.
MSLStructMemberKey key = get_struct_member_key(ib_type_id, mbr_idx);
struct_member_padding[key] = gap;
}
else if (gap < 0)
{
// If this member should be closer to the previous member than the default
// spacing expects, it is likely that the previous member is in a packed format.
// If so, and the previous member is packable, pack it.
// For example...this applies to any 3-element packed vector.
uint32_t prev_mbr_idx = mbr_idx - 1;
if (is_member_packable(ib_type, prev_mbr_idx))
set_member_decoration(ib_type_id, prev_mbr_idx, DecorationCPacked);
}
// Increment the current offset to be positioned immediately after the current member.
curr_offset = mbr_offset + uint32_t(get_declared_struct_member_size(ib_type, mbr_idx));
}
// Test the alignment of each member, and if a member is positioned farther than its
// alignment and the end of the previous member, add a dummy padding member that will
// be added before the current member when the delaration of this struct is emitted.
for (uint32_t mbr_idx = 0; mbr_idx < mbr_cnt; mbr_idx++)
{
// Align current offset to the current member's default alignment.
size_t align_mask = get_declared_struct_member_alignment(ib_type, mbr_idx) - 1;
curr_offset = uint32_t((curr_offset + align_mask) & ~align_mask);
// Fetch the member offset as declared in the SPIRV.
uint32_t mbr_offset = get_member_decoration(ib_type_id, mbr_idx, DecorationOffset);
if (mbr_offset > curr_offset)
{
// Since MSL and SPIR-V have slightly different struct member alignment and
// size rules, we'll pad to standard C-packing rules. If the member is farther
// away than C-packing, expects, add an inert padding member before the the member.
MSLStructMemberKey key = get_struct_member_key(ib_type_id, mbr_idx);
struct_member_padding[key] = mbr_offset - curr_offset;
}
// Increment the current offset to be positioned immediately after the current member.
curr_offset = mbr_offset + uint32_t(get_declared_struct_member_size(ib_type, mbr_idx));
}
}
@ -733,7 +758,7 @@ void CompilerMSL::emit_resources()
(has_decoration(type.self, DecorationBlock) || has_decoration(type.self, DecorationBufferBlock)) &&
!is_hidden_variable(var))
{
if (msl_config.pad_and_pack_uniform_structs)
if (options.pad_and_pack_uniform_structs)
align_struct(type);
emit_struct(type);
@ -1028,7 +1053,7 @@ string CompilerMSL::to_function_args(uint32_t img, const SPIRType &imgtype, bool
break;
case Dim2D:
if (msl_config.flip_frag_y)
if (options.flip_frag_y)
{
string coord_x = coord_expr + ".x";
string coord_y = coord_expr + ".y";
@ -1051,7 +1076,7 @@ string CompilerMSL::to_function_args(uint32_t img, const SPIRType &imgtype, bool
break;
case Dim3D:
if (msl_config.flip_frag_y)
if (options.flip_frag_y)
{
string coord_x = coord_expr + ".x";
string coord_y = coord_expr + ".y";
@ -1075,7 +1100,7 @@ string CompilerMSL::to_function_args(uint32_t img, const SPIRType &imgtype, bool
break;
case DimCube:
if (msl_config.flip_frag_y)
if (options.flip_frag_y)
{
string coord_x = coord_expr + ".x";
string coord_y = coord_expr + ".y";
@ -1196,7 +1221,7 @@ string CompilerMSL::to_function_args(uint32_t img, const SPIRType &imgtype, bool
switch (imgtype.image.dim)
{
case Dim2D:
if (msl_config.flip_frag_y)
if (options.flip_frag_y)
{
string coord_x = offset_expr + ".x";
string coord_y = offset_expr + ".y";
@ -1212,7 +1237,7 @@ string CompilerMSL::to_function_args(uint32_t img, const SPIRType &imgtype, bool
break;
case Dim3D:
if (msl_config.flip_frag_y)
if (options.flip_frag_y)
{
string coord_x = offset_expr + ".x";
string coord_y = offset_expr + ".y";
@ -1321,13 +1346,13 @@ void CompilerMSL::emit_fixup()
if ((execution.model == ExecutionModelVertex) && stage_out_var_id && !qual_pos_var_name.empty())
{
if (options.vertex.fixup_clipspace)
if (CompilerGLSL::options.vertex.fixup_clipspace)
{
statement(qual_pos_var_name, ".z = (", qual_pos_var_name, ".z + ", qual_pos_var_name,
".w) * 0.5; // Adjust clip-space for Metal");
}
if (msl_config.flip_vert_y)
if (options.flip_vert_y)
statement(qual_pos_var_name, ".y = -(", qual_pos_var_name, ".y);", " // Invert Y-axis for Metal");
}
}
@ -1392,7 +1417,7 @@ string CompilerMSL::member_attribute_qualifier(const SPIRType &type, uint32_t in
return " /* [[clip_distance]] built-in not yet supported under Metal. */";
case BuiltInPointSize: // Must output only if really rendering points
return msl_config.is_rendering_points ? (string(" [[") + builtin_qualifier(builtin) + "]]") : "";
return options.is_rendering_points ? (string(" [[") + builtin_qualifier(builtin) + "]]") : "";
case BuiltInPosition:
case BuiltInLayer:

View File

@ -27,15 +27,8 @@
namespace spirv_cross
{
// Options for compiling to Metal Shading Language
struct MSLConfiguration
{
bool flip_vert_y = false;
bool flip_frag_y = false;
bool is_rendering_points = false;
bool pad_and_pack_uniform_structs = false;
std::string entry_point_name;
};
// Deprecated legacy syntax
#define MSLConfiguration CompilerMSL::Options
// Defines MSL characteristics of a vertex attribute at a particular location.
// The used_by_shader flag is set to true during compilation of SPIR-V to MSL
@ -83,11 +76,28 @@ static const uint32_t kPushConstBinding = 0;
class CompilerMSL : public CompilerGLSL
{
public:
// Constructs an instance to compile the SPIR-V code into Metal Shading Language.
CompilerMSL(std::vector<uint32_t> spirv);
// Options for compiling to Metal Shading Language
struct Options
{
bool flip_vert_y = false;
bool flip_frag_y = false;
bool is_rendering_points = false;
bool pad_and_pack_uniform_structs = false;
std::string entry_point_name;
};
// Compiles the SPIR-V code into Metal Shading Language using the specified configuration parameters.
// - msl_cfg indicates some general configuration for directing the compilation.
const Options &get_options() const
{
return options;
}
void set_options(Options &opts)
{
options = opts;
}
// Constructs an instance to compile the SPIR-V code into Metal Shading Language,
// using the configuration parameters, if provided:
// - p_vtx_attrs is an optional list of vertex attribute bindings used to match
// vertex content locations to MSL attributes. If vertex attributes are provided,
// the compiler will set the used_by_shader flag to true in any vertex attribute
@ -96,12 +106,22 @@ public:
// texture or sampler index to use for a particular SPIR-V description set
// and binding. If resource bindings are provided, the compiler will set the
// used_by_shader flag to true in any resource binding actually used by the MSL code.
CompilerMSL(std::vector<uint32_t> spirv, std::vector<MSLVertexAttr> *p_vtx_attrs = nullptr,
std::vector<MSLResourceBinding> *p_res_bindings = nullptr);
// Compiles the SPIR-V code into Metal Shading Language.
std::string compile() override;
// Compiles the SPIR-V code into Metal Shading Language, overriding configuration parameters.
// Any of the parameters here may be null to indicate that the configuration provided in the
// constructor should be used. They are not declared as optional to avoid a conflict with the
// inherited and overridden zero-parameter compile() function.
std::string compile(std::vector<MSLVertexAttr> *p_vtx_attrs, std::vector<MSLResourceBinding> *p_res_bindings);
// This legacy method is deprecated.
std::string compile(MSLConfiguration &msl_cfg, std::vector<MSLVertexAttr> *p_vtx_attrs = nullptr,
std::vector<MSLResourceBinding> *p_res_bindings = nullptr);
// Compiles the SPIR-V code into Metal Shading Language using default configuration parameters.
std::string compile() override;
protected:
void emit_instruction(const Instruction &instr) override;
void emit_glsl_op(uint32_t result_type, uint32_t result_id, uint32_t op, const uint32_t *args,
@ -174,7 +194,7 @@ protected:
bool is_member_packable(SPIRType &ib_type, uint32_t index);
MSLStructMemberKey get_struct_member_key(uint32_t type_id, uint32_t index);
MSLConfiguration msl_config;
Options options;
std::unordered_map<std::string, std::string> func_name_overrides;
std::unordered_map<std::string, std::string> var_name_overrides;
std::set<uint32_t> custom_function_ops;