MSL: Add a workaround for broken level() arguments.

Some Metal devices have a bug with depth array textures using comparison
with explicit LoD, where the LoD given will be biased by some amount.
For these devices, we can use a gradient instead, which does not exhibit
this problem. As with the fragment demote workaround, this is only
expected to be needed until the bug is fixed in Metal.
This commit is contained in:
Chip Davis 2023-01-18 02:39:01 -08:00
parent 2a9091ce53
commit e8d419854f
10 changed files with 142 additions and 3 deletions

View File

@ -332,7 +332,7 @@ if (SPIRV_CROSS_STATIC)
endif()
set(spirv-cross-abi-major 0)
set(spirv-cross-abi-minor 54)
set(spirv-cross-abi-minor 55)
set(spirv-cross-abi-patch 0)
if (SPIRV_CROSS_SHARED)

View File

@ -675,6 +675,7 @@ struct CLIArguments
bool msl_force_sample_rate_shading = false;
bool msl_manual_helper_invocation_updates = true;
bool msl_check_discarded_frag_stores = false;
bool msl_sample_dref_lod_array_as_grad = false;
const char *msl_combined_sampler_suffix = nullptr;
bool glsl_emit_push_constant_as_ubo = false;
bool glsl_emit_ubo_as_plain_uniforms = false;
@ -947,6 +948,10 @@ static void print_help_msl()
"\t\tSome Metal devices have a bug where stores to resources from a fragment shader\n"
"\t\tcontinue to execute, even when the fragment is discarded. These checks\n"
"\t\tprevent these stores from executing.\n"
"\t[--msl-sample-dref-lod-array-as-grad]:\n\t\tUse a gradient instead of a level argument.\n"
"\t\tSome Metal devices have a bug where the level() argument to\n"
"\t\tdepth2d_array<T>::sample_compare() in a fragment shader is biased by some\n"
"\t\tunknown amount. This prevents the bias from being added.\n"
"\t[--msl-combined-sampler-suffix <suffix>]:\n\t\tUses a custom suffix for combined samplers.\n");
// clang-format on
}
@ -1221,6 +1226,7 @@ static string compile_iteration(const CLIArguments &args, std::vector<uint32_t>
msl_opts.force_sample_rate_shading = args.msl_force_sample_rate_shading;
msl_opts.manual_helper_invocation_updates = args.msl_manual_helper_invocation_updates;
msl_opts.check_discarded_frag_stores = args.msl_check_discarded_frag_stores;
msl_opts.sample_dref_lod_array_as_grad = args.msl_sample_dref_lod_array_as_grad;
msl_opts.ios_support_base_vertex_instance = true;
msl_comp->set_msl_options(msl_opts);
for (auto &v : args.msl_discrete_descriptor_sets)
@ -1774,6 +1780,8 @@ static int main_inner(int argc, char *argv[])
cbs.add("--msl-no-manual-helper-invocation-updates",
[&args](CLIParser &) { args.msl_manual_helper_invocation_updates = false; });
cbs.add("--msl-check-discarded-frag-stores", [&args](CLIParser &) { args.msl_check_discarded_frag_stores = true; });
cbs.add("--msl-sample-dref-lod-array-as-grad",
[&args](CLIParser &) { args.msl_sample_dref_lod_array_as_grad = true; });
cbs.add("--msl-combined-sampler-suffix", [&args](CLIParser &parser) {
args.msl_combined_sampler_suffix = parser.next_string();
});

View File

@ -0,0 +1,23 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct main0_out
{
float4 o_color [[color(0)]];
};
struct main0_in
{
float3 v_texCoord [[user(locn0)]];
float v_lodBias [[user(locn1)]];
};
fragment main0_out main0(main0_in in [[stage_in]], depth2d_array<float> u_sampler [[texture(0)]], sampler u_samplerSmplr [[sampler(0)]])
{
main0_out out = {};
out.o_color = float4(u_sampler.sample_compare(u_samplerSmplr, float2(in.v_texCoord.x, 0.5), uint(rint(in.v_texCoord.y)), in.v_texCoord.z, gradient2d(exp2(in.v_lodBias - 0.5) / float2(u_sampler.get_width(), 1.0), exp2(in.v_lodBias - 0.5) / float2(u_sampler.get_width(), 1.0))), 0.0, 0.0, 1.0);
return out;
}

View File

@ -0,0 +1,33 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct buf0
{
float4 u_scale;
};
struct buf1
{
float4 u_bias;
};
struct main0_out
{
float4 o_color [[color(0)]];
};
struct main0_in
{
float3 v_texCoord [[user(locn0)]];
float v_lodBias [[user(locn1)]];
};
fragment main0_out main0(main0_in in [[stage_in]], depth2d_array<float> u_sampler [[texture(0)]], sampler u_samplerSmplr [[sampler(0)]])
{
main0_out out = {};
out.o_color = float4(u_sampler.sample_compare(u_samplerSmplr, float2(in.v_texCoord.x, 0.5), uint(rint(in.v_texCoord.y)), in.v_texCoord.z, gradient2d(exp2(in.v_lodBias - 0.5) / float2(u_sampler.get_width(), 1.0), exp2(in.v_lodBias - 0.5) / float2(u_sampler.get_width(), 1.0))), 0.0, 0.0, 1.0);
return out;
}

View File

@ -0,0 +1,12 @@
#version 450 core
layout(location = 0) out mediump vec4 o_color;
layout(location = 0) in highp vec3 v_texCoord;
layout(location = 1) in highp float v_lodBias;
layout(set = 0, binding = 0) uniform highp sampler1DArrayShadow u_sampler;
layout(set = 0, binding = 1) uniform buf0 { highp vec4 u_scale; };
layout(set = 0, binding = 2) uniform buf1 { highp vec4 u_bias; };
void main()
{
o_color = vec4(textureLod(u_sampler, v_texCoord, v_lodBias), 0.0, 0.0, 1.0);
}

View File

@ -738,6 +738,10 @@ spvc_result spvc_compiler_options_set_uint(spvc_compiler_options options, spvc_c
case SPVC_COMPILER_OPTION_MSL_ARGUMENT_BUFFERS_TIER:
options->msl.argument_buffers_tier = static_cast<CompilerMSL::Options::ArgumentBuffersTier>(value);
break;
case SPVC_COMPILER_OPTION_MSL_SAMPLE_DREF_LOD_ARRAY_AS_GRAD:
options->msl.sample_dref_lod_array_as_grad = value != 0;
break;
#endif
default:

View File

@ -40,7 +40,7 @@ extern "C" {
/* Bumped if ABI or API breaks backwards compatibility. */
#define SPVC_C_API_VERSION_MAJOR 0
/* Bumped if APIs or enumerations are added in a backwards compatible way. */
#define SPVC_C_API_VERSION_MINOR 54
#define SPVC_C_API_VERSION_MINOR 55
/* Bumped if internal implementation details change. */
#define SPVC_C_API_VERSION_PATCH 0
@ -724,6 +724,7 @@ typedef enum spvc_compiler_option
SPVC_COMPILER_OPTION_GLSL_ENABLE_ROW_MAJOR_LOAD_WORKAROUND = 83 | SPVC_COMPILER_OPTION_GLSL_BIT,
SPVC_COMPILER_OPTION_MSL_ARGUMENT_BUFFERS_TIER = 84 | SPVC_COMPILER_OPTION_MSL_BIT,
SPVC_COMPILER_OPTION_MSL_SAMPLE_DREF_LOD_ARRAY_AS_GRAD = 85 | SPVC_COMPILER_OPTION_MSL_BIT,
SPVC_COMPILER_OPTION_INT_MAX = 0x7fffffff
} spvc_compiler_option;

View File

@ -10823,7 +10823,8 @@ string CompilerMSL::to_function_args(const TextureFunctionArguments &args, bool
// We will detect a compile-time constant 0 value for gradient and promote that to level(0) on MSL.
bool constant_zero_x = !grad_x || expression_is_constant_null(grad_x);
bool constant_zero_y = !grad_y || expression_is_constant_null(grad_y);
if (constant_zero_x && constant_zero_y)
if (constant_zero_x && constant_zero_y &&
(!imgtype.image.arrayed || !msl_options.sample_dref_lod_array_as_grad))
{
lod = 0;
grad_x = 0;
@ -10869,6 +10870,52 @@ string CompilerMSL::to_function_args(const TextureFunctionArguments &args, bool
{
farg_str += ", " + to_expression(lod);
}
else if (msl_options.sample_dref_lod_array_as_grad && args.dref && imgtype.image.arrayed)
{
if (msl_options.is_macos() && !msl_options.supports_msl_version(2, 3))
SPIRV_CROSS_THROW("Using non-constant 0.0 gradient() qualifier for sample_compare. This is not "
"supported on macOS prior to MSL 2.3.");
// Some Metal devices have a bug where the LoD is erroneously biased upward
// when using a level() argument. Since this doesn't happen as much with gradient2d(),
// if we perform the LoD calculation in reverse, we can pass a gradient
// instead.
// lod = log2(rhoMax/eta) -> exp2(lod) = rhoMax/eta
// If we make all of the scale factors the same, eta will be 1 and
// exp2(lod) = rho.
// rhoX = dP/dx * extent; rhoY = dP/dy * extent
// Therefore, dP/dx = dP/dy = exp2(lod)/extent.
// (Subtracting 0.5 before exponentiation gives better results.)
string grad_opt, extent;
switch (imgtype.image.dim)
{
case Dim1D:
grad_opt = "2d";
extent = join("float2(", to_expression(img), ".get_width(), 1.0)");
break;
case Dim2D:
grad_opt = "2d";
extent = join("float2(", to_expression(img), ".get_width(), ", to_expression(img), ".get_height())");
break;
case DimCube:
if (imgtype.image.arrayed && msl_options.emulate_cube_array)
{
grad_opt = "2d";
extent = join("float2(", to_expression(img), ".get_width())");
}
else
{
grad_opt = "cube";
extent = join("float3(", to_expression(img), ".get_width())");
}
break;
default:
grad_opt = "unsupported_gradient_dimension";
extent = "float3(1.0)";
break;
}
farg_str += join(", gradient", grad_opt, "(exp2(", to_expression(lod), " - 0.5) / ", extent, ", exp2(",
to_expression(lod), " - 0.5) / ", extent, ")");
}
else
{
farg_str += ", level(" + to_expression(lod) + ")";

View File

@ -487,6 +487,15 @@ public:
// only when the bug is present.
bool check_discarded_frag_stores = false;
// If set, Lod operands to OpImageSample*DrefExplicitLod for 1D and 2D array images
// will be implemented using a gradient instead of passing the level operand directly.
// Some Metal devices have a bug where the level() argument to depth2d_array<T>::sample_compare()
// in a fragment shader is biased by some unknown amount, possibly dependent on the
// partial derivatives of the texture coordinates. This is a workaround that is only
// expected to be needed until the bug is fixed in Metal; it is provided as an option
// so it can be enabled only when the bug is present.
bool sample_dref_lod_array_as_grad = false;
bool is_ios() const
{
return platform == iOS;

View File

@ -344,6 +344,8 @@ def cross_compile_msl(shader, spirv, opt, iterations, paths):
msl_args.append('--msl-force-sample-rate-shading')
if '.discard-checks.' in shader:
msl_args.append('--msl-check-discarded-frag-stores')
if '.lod-as-grad.' in shader:
msl_args.append('--msl-sample-dref-lod-array-as-grad')
if '.decoration-binding.' in shader:
msl_args.append('--msl-decoration-binding')
if '.mask-location-0.' in shader: