Further updates for pull request #1162; also added two test cases for spvCubemapTo2DArrayFace function and added '--msl-framebuffer-fetch'/ '--msl-emulate-cube-array' compiler options.

This commit is contained in:
Lukas Hermanns 2019-09-27 15:49:54 -04:00
parent c3d6022956
commit f3a6d28a1d
15 changed files with 276 additions and 66 deletions

View File

@ -514,6 +514,8 @@ struct CLIArguments
bool msl_domain_lower_left = false;
bool msl_argument_buffers = false;
bool msl_texture_buffer_native = false;
bool msl_framebuffer_fetch = false;
bool msl_emulate_cube_array = false;
bool msl_multiview = false;
bool msl_view_index_from_device_index = false;
bool msl_dispatch_base = false;
@ -597,6 +599,8 @@ static void print_help()
"\t[--msl-domain-lower-left]\n"
"\t[--msl-argument-buffers]\n"
"\t[--msl-texture-buffer-native]\n"
"\t[--msl-framebuffer-fetch]\n"
"\t[--msl-emulate-cube-array]\n"
"\t[--msl-discrete-descriptor-set <index>]\n"
"\t[--msl-multiview]\n"
"\t[--msl-view-index-from-device-index]\n"
@ -755,7 +759,11 @@ static string compile_iteration(const CLIArguments &args, std::vector<uint32_t>
msl_opts.capture_output_to_buffer = args.msl_capture_output_to_buffer;
msl_opts.swizzle_texture_samples = args.msl_swizzle_texture_samples;
if (args.msl_ios)
{
msl_opts.platform = CompilerMSL::Options::iOS;
msl_opts.ios_use_framebuffer_fetch_subpasses = args.msl_framebuffer_fetch;
msl_opts.emulate_cube_array = args.msl_emulate_cube_array;
}
msl_opts.pad_fragment_output_components = args.msl_pad_fragment_output;
msl_opts.tess_domain_origin_lower_left = args.msl_domain_lower_left;
msl_opts.argument_buffers = args.msl_argument_buffers;
@ -1087,6 +1095,8 @@ 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-framebuffer-fetch", [&args](CLIParser &) { args.msl_framebuffer_fetch = true; });
cbs.add("--msl-emulate-cube-array", [&args](CLIParser &) { args.msl_emulate_cube_array = true; });
cbs.add("--msl-multiview", [&args](CLIParser &) { args.msl_multiview = true; });
cbs.add("--msl-view-index-from-device-index",
[&args](CLIParser &) { args.msl_view_index_from_device_index = true; });

View File

@ -238,10 +238,10 @@ struct main0_out
float4 out_var_SV_Target0 [[color(0)]];
};
fragment main0_out main0(constant type_View& View [[buffer(0)]], constant type_Globals& _Globals [[buffer(1)]], texture2d<float> _gl_LastFragData [[texture(0)]], texture2d<float> ShadowDepthTexture [[texture(1)]], sampler ShadowDepthTextureSampler [[sampler(0)]], float4 gl_FragCoord [[position]])
fragment main0_out main0(constant type_View& View [[buffer(0)]], constant type_Globals& _Globals [[buffer(1)]], float4 _gl_LastFragData [[color(0)]], texture2d<float> ShadowDepthTexture [[texture(1)]], sampler ShadowDepthTextureSampler [[sampler(0)]], float4 gl_FragCoord [[position]])
{
main0_out out = {};
float4 _67 = _gl_LastFragData.read(uint2(gl_FragCoord.xy), 0);
float4 _67 = _gl_LastFragData;
float _68 = _67.w;
float4 _82 = _Globals.ScreenToShadowMatrix * float4((((gl_FragCoord.xy * View.View_BufferSizeAndInvSize.zw) - View.View_ScreenPositionScaleBias.wz) / View.View_ScreenPositionScaleBias.xy) * float2(_68), _68, 1.0);
float _118 = fast::clamp(((fast::clamp((ShadowDepthTexture.sample(ShadowDepthTextureSampler, (((_82.xyz / float3(_82.w)).xy * _Globals.ShadowTileOffsetAndSize.zw).xy + _Globals.ShadowTileOffsetAndSize.xy).xy, level(0.0)).xxx * float3(_Globals.SoftTransitionScale.z)) - float3((fast::min(_82.z, 0.999989986419677734375) * _Globals.SoftTransitionScale.z) - 1.0), float3(0.0), float3(1.0)).x - 0.5) * _Globals.ShadowSharpen) + 0.5, 0.0, 1.0);

View File

@ -0,0 +1,22 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct main0_out
{
float4 FragColor [[color(0)]];
};
struct main0_in
{
float4 vUV [[user(locn0)]];
};
fragment main0_out main0(main0_in in [[stage_in]], texturecube<float> cubeSampler [[texture(0)]], texturecube_array<float> cubeArraySampler [[texture(1)]], texture2d_array<float> texArraySampler [[texture(2)]], sampler cubeSamplerSmplr [[sampler(0)]], sampler cubeArraySamplerSmplr [[sampler(1)]], sampler texArraySamplerSmplr [[sampler(2)]])
{
main0_out out = {};
out.FragColor = (cubeSampler.sample(cubeSamplerSmplr, in.vUV.xyz) + cubeArraySampler.sample(cubeArraySamplerSmplr, in.vUV.xyz, uint(round(in.vUV.w)))) + texArraySampler.sample(texArraySamplerSmplr, in.vUV.xyz.xy, uint(round(in.vUV.xyz.z)));
return out;
}

View File

@ -0,0 +1,58 @@
#pragma clang diagnostic ignored "-Wmissing-prototypes"
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct main0_out
{
float4 FragColor [[color(0)]];
};
struct main0_in
{
float4 vUV [[user(locn0)]];
};
static inline __attribute__((always_inline))
float3 spvCubemapTo2DArrayFace(float3 P)
{
float3 Coords = abs(P.xyz);
float CubeFace = 0;
float ProjectionAxis = 0;
float u = 0;
float v = 0;
if (Coords.x >= Coords.y && Coords.x >= Coords.z)
{
CubeFace = P.x >= 0 ? 0 : 1;
ProjectionAxis = Coords.x;
u = P.x >= 0 ? -P.z : P.z;
v = -P.y;
}
else if (Coords.y >= Coords.x && Coords.y >= Coords.z)
{
CubeFace = P.y >= 0 ? 2 : 3;
ProjectionAxis = Coords.y;
u = P.x;
v = P.y >= 0 ? P.z : -P.z;
}
else
{
CubeFace = P.z >= 0 ? 4 : 5;
ProjectionAxis = Coords.z;
u = P.z >= 0 ? P.x : -P.x;
v = -P.y;
}
u = 0.5 * (u/ProjectionAxis + 1);
v = 0.5 * (v/ProjectionAxis + 1);
return float3(u, v, CubeFace);
}
fragment main0_out main0(main0_in in [[stage_in]], texturecube<float> cubeSampler [[texture(0)]], texture2d_array<float> cubeArraySampler [[texture(1)]], texture2d_array<float> texArraySampler [[texture(2)]], sampler cubeSamplerSmplr [[sampler(0)]], sampler cubeArraySamplerSmplr [[sampler(1)]], sampler texArraySamplerSmplr [[sampler(2)]])
{
main0_out out = {};
out.FragColor = (cubeSampler.sample(cubeSamplerSmplr, in.vUV.xyz) + cubeArraySampler.sample(cubeArraySamplerSmplr, spvCubemapTo2DArrayFace(in.vUV.xyz).xy, uint(spvCubemapTo2DArrayFace(in.vUV.xyz).z) + (uint(round(in.vUV.w)) * 6u))) + texArraySampler.sample(texArraySamplerSmplr, in.vUV.xyz.xy, uint(round(in.vUV.xyz.z)));
return out;
}

View File

@ -238,10 +238,10 @@ struct main0_out
float4 out_var_SV_Target0 [[color(0)]];
};
fragment main0_out main0(constant type_View& View [[buffer(0)]], constant type_Globals& _Globals [[buffer(1)]], texture2d<float> _gl_LastFragData [[texture(0)]], texture2d<float> ShadowDepthTexture [[texture(1)]], sampler ShadowDepthTextureSampler [[sampler(0)]], float4 gl_FragCoord [[position]])
fragment main0_out main0(constant type_View& View [[buffer(0)]], constant type_Globals& _Globals [[buffer(1)]], float4 _gl_LastFragData [[color(0)]], texture2d<float> ShadowDepthTexture [[texture(1)]], sampler ShadowDepthTextureSampler [[sampler(0)]], float4 gl_FragCoord [[position]])
{
main0_out out = {};
float4 _67 = _gl_LastFragData.read(uint2(gl_FragCoord.xy), 0);
float4 _67 = _gl_LastFragData;
float _68 = _67.w;
float4 _82 = _Globals.ScreenToShadowMatrix * float4((((gl_FragCoord.xy * View.View_BufferSizeAndInvSize.zw) - View.View_ScreenPositionScaleBias.wz) / View.View_ScreenPositionScaleBias.xy) * float2(_68), _68, 1.0);
float _118 = fast::clamp(((fast::clamp((ShadowDepthTexture.sample(ShadowDepthTextureSampler, (((_82.xyz / float3(_82.w)).xy * _Globals.ShadowTileOffsetAndSize.zw).xy + _Globals.ShadowTileOffsetAndSize.xy).xy, level(0.0)).xxx * float3(_Globals.SoftTransitionScale.z)) - float3((fast::min(_82.z, 0.999989986419677734375) * _Globals.SoftTransitionScale.z) - 1.0), float3(0.0), float3(1.0)).x - 0.5) * _Globals.ShadowSharpen) + 0.5, 0.0, 1.0);

View File

@ -0,0 +1,25 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct main0_out
{
float4 FragColor [[color(0)]];
};
struct main0_in
{
float4 vUV [[user(locn0)]];
};
fragment main0_out main0(main0_in in [[stage_in]], texturecube<float> cubeSampler [[texture(0)]], texturecube_array<float> cubeArraySampler [[texture(1)]], texture2d_array<float> texArraySampler [[texture(2)]], sampler cubeSamplerSmplr [[sampler(0)]], sampler cubeArraySamplerSmplr [[sampler(1)]], sampler texArraySamplerSmplr [[sampler(2)]])
{
main0_out out = {};
float4 a = cubeSampler.sample(cubeSamplerSmplr, in.vUV.xyz);
float4 b = cubeArraySampler.sample(cubeArraySamplerSmplr, in.vUV.xyz, uint(round(in.vUV.w)));
float4 c = texArraySampler.sample(texArraySamplerSmplr, in.vUV.xyz.xy, uint(round(in.vUV.xyz.z)));
out.FragColor = (a + b) + c;
return out;
}

View File

@ -0,0 +1,61 @@
#pragma clang diagnostic ignored "-Wmissing-prototypes"
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct main0_out
{
float4 FragColor [[color(0)]];
};
struct main0_in
{
float4 vUV [[user(locn0)]];
};
static inline __attribute__((always_inline))
float3 spvCubemapTo2DArrayFace(float3 P)
{
float3 Coords = abs(P.xyz);
float CubeFace = 0;
float ProjectionAxis = 0;
float u = 0;
float v = 0;
if (Coords.x >= Coords.y && Coords.x >= Coords.z)
{
CubeFace = P.x >= 0 ? 0 : 1;
ProjectionAxis = Coords.x;
u = P.x >= 0 ? -P.z : P.z;
v = -P.y;
}
else if (Coords.y >= Coords.x && Coords.y >= Coords.z)
{
CubeFace = P.y >= 0 ? 2 : 3;
ProjectionAxis = Coords.y;
u = P.x;
v = P.y >= 0 ? P.z : -P.z;
}
else
{
CubeFace = P.z >= 0 ? 4 : 5;
ProjectionAxis = Coords.z;
u = P.z >= 0 ? P.x : -P.x;
v = -P.y;
}
u = 0.5 * (u/ProjectionAxis + 1);
v = 0.5 * (v/ProjectionAxis + 1);
return float3(u, v, CubeFace);
}
fragment main0_out main0(main0_in in [[stage_in]], texturecube<float> cubeSampler [[texture(0)]], texture2d_array<float> cubeArraySampler [[texture(1)]], texture2d_array<float> texArraySampler [[texture(2)]], sampler cubeSamplerSmplr [[sampler(0)]], sampler cubeArraySamplerSmplr [[sampler(1)]], sampler texArraySamplerSmplr [[sampler(2)]])
{
main0_out out = {};
float4 a = cubeSampler.sample(cubeSamplerSmplr, in.vUV.xyz);
float4 b = cubeArraySampler.sample(cubeArraySamplerSmplr, spvCubemapTo2DArrayFace(in.vUV.xyz).xy, uint(spvCubemapTo2DArrayFace(in.vUV.xyz).z) + (uint(round(in.vUV.w)) * 6u));
float4 c = texArraySampler.sample(texArraySamplerSmplr, in.vUV.xyz.xy, uint(round(in.vUV.xyz.z)));
out.FragColor = (a + b) + c;
return out;
}

View File

@ -0,0 +1,16 @@
#version 450
layout(set = 0, binding = 0) uniform samplerCube cubeSampler;
layout(set = 0, binding = 1) uniform samplerCubeArray cubeArraySampler;
layout(set = 0, binding = 2) uniform sampler2DArray texArraySampler;
layout(location = 0) in vec4 vUV;
layout(location = 0) out vec4 FragColor;
void main()
{
vec4 a = texture(cubeSampler, vUV.xyz);
vec4 b = texture(cubeArraySampler, vUV);
vec4 c = texture(texArraySampler, vUV.xyz);
FragColor = a + b + c;
}

View File

@ -0,0 +1,16 @@
#version 450
layout(set = 0, binding = 0) uniform samplerCube cubeSampler;
layout(set = 0, binding = 1) uniform samplerCubeArray cubeArraySampler;
layout(set = 0, binding = 2) uniform sampler2DArray texArraySampler;
layout(location = 0) in vec4 vUV;
layout(location = 0) out vec4 FragColor;
void main()
{
vec4 a = texture(cubeSampler, vUV.xyz);
vec4 b = texture(cubeArraySampler, vUV);
vec4 c = texture(texArraySampler, vUV.xyz);
FragColor = a + b + c;
}

View File

@ -487,9 +487,6 @@ public:
// The most common use here is to check if a buffer is readonly or writeonly.
Bitset get_buffer_block_flags(VariableID id) const;
// Returns true if the target language supports combined texture-samplers. Returns fasle by default.
virtual bool supports_combined_samplers() const;
protected:
const uint32_t *stream(const Instruction &instr) const
{
@ -615,6 +612,9 @@ protected:
void register_read(uint32_t expr, uint32_t chain, bool forwarded);
void register_write(uint32_t chain);
// Returns true if the target language supports combined texture-samplers. Returns fasle by default.
virtual bool supports_combined_samplers() const;
inline bool is_continue(uint32_t next) const
{
return (ir.block_meta[next] & ParsedIR::BLOCK_META_CONTINUE_BIT) != 0;

View File

@ -3437,6 +3437,11 @@ string CompilerGLSL::constant_expression(const SPIRConstant &c)
return res;
}
else if (type.basetype == SPIRType::Struct && type.member_types.size() == 0)
{
// Metal tessellation likes empty structs which are then constant expressions.
return "{ }";
}
else if (c.columns() == 1)
{
return constant_expression_vector(c, 0);
@ -4023,18 +4028,6 @@ string CompilerGLSL::constant_expression_vector(const SPIRConstant &c, uint32_t
}
break;
// Metal tessellation likes empty structs which are then constant expressions.
case SPIRType::Struct:
if (type.member_types.size() == 0)
{
res += "{ }";
}
else
{
SPIRV_CROSS_THROW("Invalid constant struct initialisation missing member initializers.");
}
break;
default:
SPIRV_CROSS_THROW("Invalid constant expression basetype.");
}

View File

@ -4782,7 +4782,7 @@ void CompilerMSL::declare_undefined_values()
bool emitted = false;
ir.for_each_typed_id<SPIRUndef>([&](uint32_t, SPIRUndef &undef) {
auto &type = this->get<SPIRType>(undef.basetype);
statement("constant ", CompilerGLSL::variable_decl(type, to_name(undef.self), undef.self), " = {};");
statement("constant ", variable_decl(type, to_name(undef.self), undef.self), " = {};");
emitted = true;
});
@ -4805,7 +4805,7 @@ void CompilerMSL::declare_constant_arrays()
if (!type.array.empty() && (is_scalar(type) || is_vector(type)))
{
auto name = to_name(c.self);
statement("constant ", CompilerGLSL::variable_decl(type, name), " = ", constant_expression(c), ";");
statement("constant ", variable_decl(type, name), " = ", constant_expression(c), ";");
emitted = true;
}
});
@ -4829,7 +4829,7 @@ void CompilerMSL::declare_complex_constant_arrays()
if (!type.array.empty() && !(is_scalar(type) || is_vector(type)))
{
auto name = to_name(c.self);
statement("", CompilerGLSL::variable_decl(type, name), " = ", constant_expression(c), ";");
statement("", variable_decl(type, name), " = ", constant_expression(c), ";");
emitted = true;
}
});
@ -4940,7 +4940,7 @@ void CompilerMSL::emit_specialization_constants_and_structs()
auto &c = id.get<SPIRConstantOp>();
auto &type = get<SPIRType>(c.basetype);
auto name = to_name(c.self);
statement("constant ", CompilerGLSL::variable_decl(type, name), " = ", constant_op_expression(c), ";");
statement("constant ", variable_decl(type, name), " = ", constant_op_expression(c), ";");
emitted = true;
}
else if (id.get_type() == TypeType)
@ -5066,7 +5066,7 @@ bool CompilerMSL::emit_tessellation_access_chain(const uint32_t *ops, uint32_t l
if (is_matrix(*type) || is_array(*type) || type->basetype == SPIRType::Struct)
{
std::string temp_name = join(to_name(var->self), "_", ops[1]);
statement(CompilerGLSL::variable_decl(*type, temp_name, var->self), ";");
statement(variable_decl(*type, temp_name, var->self), ";");
// Set up the initializer for this temporary variable.
indices.push_back(const_mbr_id);
if (type->basetype == SPIRType::Struct)
@ -6123,43 +6123,32 @@ void CompilerMSL::emit_instruction(const Instruction &instruction)
void CompilerMSL::emit_texture_op(const Instruction &i)
{
auto *ops = stream(i);
auto op = static_cast<Op>(i.op);
SmallVector<uint32_t> inherited_expressions;
uint32_t result_type_id = ops[0];
uint32_t id = ops[1];
uint32_t img = ops[2];
auto &type = expression_type(img);
auto &imgtype = get<SPIRType>(type.self);
bool forward = false;
string expr = to_texture_op(i, &forward, inherited_expressions);
// Use Metal's native frame-buffer fetch API for subpass inputs.
if (imgtype.image.dim == DimSubpassData && msl_options.is_ios() && msl_options.ios_use_framebuffer_fetch_subpasses)
if (msl_options.is_ios() && msl_options.ios_use_framebuffer_fetch_subpasses)
{
expr = to_expression(img);
auto *ops = stream(i);
uint32_t result_type_id = ops[0];
uint32_t id = ops[1];
uint32_t img = ops[2];
auto &type = expression_type(img);
auto &imgtype = get<SPIRType>(type.self);
// Use Metal's native frame-buffer fetch API for subpass inputs.
if (imgtype.image.dim == DimSubpassData)
{
SmallVector<uint32_t> inherited_expressions;
bool forward = false;
to_texture_op(i, &forward, inherited_expressions);
string expr = to_expression(img);
emit_op(result_type_id, id, expr, forward);
return;
}
}
emit_op(result_type_id, id, expr, forward);
for (auto &inherit : inherited_expressions)
inherit_expression_dependencies(id, inherit);
switch (op)
{
case OpImageSampleDrefImplicitLod:
case OpImageSampleImplicitLod:
case OpImageSampleProjImplicitLod:
case OpImageSampleProjDrefImplicitLod:
register_control_dependent_expression(id);
break;
default:
break;
}
// Fallback to default implementation
CompilerGLSL::emit_texture_op(i);
}
void CompilerMSL::emit_barrier(uint32_t id_exe_scope, uint32_t id_mem_scope, uint32_t id_mem_sem)
@ -7261,7 +7250,7 @@ string CompilerMSL::to_function_args(VariableID img, const SPIRType &imgtype, bo
if (!farg_str.empty())
farg_str += ", ";
if (imgtype.image.arrayed && msl_options.emulate_cube_array)
if (imgtype.image.dim == DimCube && imgtype.image.arrayed && msl_options.emulate_cube_array)
{
farg_str += "spvCubemapTo2DArrayFace(" + tex_coords + ").xy";
@ -7269,7 +7258,7 @@ string CompilerMSL::to_function_args(VariableID img, const SPIRType &imgtype, bo
farg_str += ", uint(" + to_extract_component_expression(coord, 2) + ")";
else
farg_str += ", uint(spvCubemapTo2DArrayFace(" + tex_coords + ").z) + (uint(" +
to_extract_component_expression(coord, 2) + ") * 6u)";
round_fp_tex_coords(to_extract_component_expression(coord, alt_coord_component), coord_is_fp) + ") * 6u)";
add_spv_func_and_recompile(SPVFuncImplCubemapTo2DArrayFace);
}
@ -10275,6 +10264,12 @@ std::string CompilerMSL::variable_decl(const SPIRVariable &variable)
return expr;
}
// GCC workaround of lambdas calling protected funcs
std::string CompilerMSL::variable_decl(const SPIRType &type, const std::string &name, uint32_t id)
{
return CompilerGLSL::variable_decl(type, name, id);
}
std::string CompilerMSL::sampler_type(const SPIRType &type)
{
if (!type.array.empty())
@ -10395,11 +10390,14 @@ string CompilerMSL::image_type_glsl(const SPIRType &type, uint32_t id)
else
img_type_name += "texture2d";
break;
case Dim2D:
case DimSubpassData:
// Use Metal's native frame-buffer fetch API for subpass inputs.
if (msl_options.is_ios() && msl_options.ios_use_framebuffer_fetch_subpasses)
if (img_type.dim == DimSubpassData && msl_options.is_ios() &&
msl_options.ios_use_framebuffer_fetch_subpasses)
{
return type_to_glsl(get<SPIRType>(img_type.type));
case Dim2D:
}
if (img_type.ms && img_type.arrayed)
{
if (!msl_options.supports_msl_version(2, 1))

View File

@ -291,7 +291,7 @@ public:
bool ios_support_base_vertex_instance = false;
// Use Metal's native frame-buffer fetch API for subpass inputs.
bool ios_use_framebuffer_fetch_subpasses = true;
bool ios_use_framebuffer_fetch_subpasses = false;
// Storage buffer robustness - clamps access to SSBOs to the size of the buffer
bool enforce_storge_buffer_bounds = false;
@ -576,9 +576,16 @@ protected:
const std::string &qualifier = "", uint32_t base_offset = 0) override;
void emit_struct_padding_target(const SPIRType &type) override;
std::string type_to_glsl(const SPIRType &type, uint32_t id = 0) override;
std::string type_to_array_glsl(
const SPIRType &type) override; // Allow Metal to use the array<T> template to make arrays a value type
std::string variable_decl(const SPIRVariable &variable) override; // Threadgroup arrays can't have a wrapper type
// Allow Metal to use the array<T> template to make arrays a value type
std::string type_to_array_glsl(const SPIRType &type) override;
// Threadgroup arrays can't have a wrapper type
std::string variable_decl(const SPIRVariable &variable) override;
// GCC workaround of lambdas calling protected functions (for older GCC versions)
std::string variable_decl(const SPIRType &type, const std::string &name, uint32_t id = 0) override;
std::string image_type_glsl(const SPIRType &type, uint32_t id = 0) override;
std::string sampler_type(const SPIRType &type);
std::string builtin_to_glsl(spv::BuiltIn builtin, spv::StorageClass storage) override;

View File

@ -195,6 +195,10 @@ def cross_compile_msl(shader, spirv, opt, iterations, paths):
msl_args.append('--msl-argument-buffers')
if '.texture-buffer-native.' in shader:
msl_args.append('--msl-texture-buffer-native')
if '.framebuffer-fetch.' in shader:
msl_args.append('--msl-framebuffer-fetch')
if '.emulate-cube-array.' in shader:
msl_args.append('--msl-emulate-cube-array')
if '.discrete.' in shader:
# Arbitrary for testing purposes.
msl_args.append('--msl-discrete-descriptor-set')